1 // This file is written in D programming language
2 /**
3 *   Module defines facilities for custom styling of logger messages. Template mixin $(generateStyle)
4 *   generates code for your logging styles (represented by an enum) that can be mixed into logger
5 *   implementation.
6 *
7 *   See $(B generateStyle) for detailed description.
8 *
9 *   Example of default logger style:
10 *   -------------------------------
11 *   import dlogg.log, dlogg.style;
12 *
13 *   mixin generateStyle!(LoggingLevel
14 *               , LoggingLevel.Debug,   "Debug: %1$s",   "[%2$s] Debug: %1$s"
15 *               , LoggingLevel.Notice,  "Notice: %1$s",  "[%2$s] Notice: %1$s"
16 *               , LoggingLevel.Warning, "Warning: %1$s", "[%2$s] Warning: %1$s"
17 *               , LoggingLevel.Fatal,   "Fatal: %1$s",   "[%2$s] Fatal: %1$s"
18 *               , LoggingLevel.Muted,   "",              ""
19 *               );
20 *   -------------------------------
21 *
22 *   Copyright: © 2013-2014 Anton Gushcha
23 *   License: Subject to the terms of the MIT license, as written in the included LICENSE file.
24 *   Authors: NCrashed <ncrashed@gmail.com>
25 */
26 module dlogg.style;
27 
28 /**
29 *   Utility mixin template that generates facilities for output message formatting
30 *   for console output and file output.
31 *
32 *   $(B Style) parameter defines what type is used as logging level. It must be an enum.
33 *   Order of values defines behavior of muting (styles that less current low border aren't
34 *   printed to output).
35 *
36 *   $(TS) has format of list of triples ($(B Style) value, string, string). Style value
37 *   defines for which logging level following format strings are. First format string is used
38 *   for console output, the second one is for file output.
39 *
40 *   Format strings could use two arguments: '%1$s' is message that is passed to a logger and
41 *   '%2$s' is current time string. Formatting is handled by $(B std.format) module. 
42 */
43 mixin template generateStyle(Style, TS...)
44 {
45     import std.array;
46     import std.traits;
47     import std.datetime;
48     import std.format;
49     import std.conv;
50 
51     /// Could not see style symbol while using with external packages
52     mixin("import "~moduleName!Style~" : "~Style.stringof~";");
53     
54     static assert(is(Style == enum), "First parameter '"~Style.stringof~"' is expected to be an enum type!");
55     static assert(checkTypes!TS, "logStyle expected triples of ('"~Style.stringof~"', string, string)");
56     static assert(checkCoverage!TS, "logStyle triples doesn't cover all '"~Style.stringof~"' cases!");
57     
58     /// Checks types of US to be Style, string, string triples
59     private template checkTypes(US...)
60     {
61         static if(US.length == 0)
62         {
63             enum checkTypes = true;
64         } else static if(US.length < 3)
65         {
66             enum checkTypes = false;
67         } else
68         {
69             enum checkTypes = is(Unqual!(typeof(US[0])) == Style) && isSomeString!(typeof(US[1])) && isSomeString!(typeof(US[2]))
70                 && checkTypes!(US[3..$]);
71         }
72     }
73     
74     /// Checking that triples covers all Style members
75     private template checkCoverage(US...)
76     {
77         /// To be able to pass two expression tuples in one template
78         private template Wrapper(T...)
79         {
80             alias get = T;
81         }
82         
83         /// Finding ZSS[0] in ZSS[1..$], false if not finded
84         private template findMember(ZSS...)
85         {
86             enum member = ZSS[0];
87             alias ZS = ZSS[1..$];
88             
89             static if(ZS.length == 0)
90             {
91                 enum findMember = false;
92             } else
93             {
94                 static if(ZS[0] == member)
95                 {
96                     enum findMember = true;
97                 } else
98                 {
99                     enum findMember = findMember!(member, ZS[1..$]);
100                 }
101             }
102         }
103         
104         /// Iterating over USS[0] to find each in USS[1]
105         /// Wrapper is used to wrap expression tuples in expression tuple
106         private template iterate(USS...)
107         {
108             alias EMembers = USS[0];
109             alias SMembers = USS[1];
110             
111             static if(EMembers.get.length == 0)
112             {
113                 enum iterate = true;
114             } else
115             {
116                 enum iterate = findMember!(EMembers.get[0], SMembers.get) 
117                     && iterate!(Wrapper!(EMembers.get[1..$]), SMembers);
118             }
119         }
120         
121         /// We interested in only each first value of each triple
122         /// creates new expression tuple from only first triple values of Style
123         private template filter(US...)
124         {
125             private template Tuple(E...)
126             {
127                 alias Tuple = E;
128             }
129             
130             static if(US.length == 0)
131             {
132                 alias filter = Tuple!();
133             } else static if(US.length < 3)
134             {
135                 static assert(false, "US invalid size!");
136             } else
137             {
138                 alias filter = Tuple!(US[0], filter!(US[3..$]));
139             }
140         }
141         
142         enum checkCoverage = iterate!(Wrapper!(EnumMembers!Style), Wrapper!(filter!US));
143     }
144     
145     private template genSwitch(USS...)
146     {
147         enum variable = USS[0];
148         enum formatElemIndex = USS[1];
149         enum messageVariable = USS[2];
150         enum timeVariable = USS[3];
151         alias US = USS[4..$];
152         
153         private template genCases(US...)
154         {
155             static if(US.length == 0)
156             {
157                 enum genCases = "";
158             } else static if(US.length < 3)
159             {
160                 static assert(false, "US invalid size!");
161             } else
162             {
163                 enum genCases = "\tcase("~Style.stringof~"."~US[0].to!string~"):\n\t{\n\t\t"
164                     ~ `writer.formattedWrite("`~US[formatElemIndex]~`", `~messageVariable~", "~timeVariable~");\n\t\t"
165                     ~ "break;\n\t}\n"
166                     ~ genCases!(US[3..$]);
167             }
168         }
169         
170         enum genSwitch = "final switch("~variable~")\n{\n" 
171             ~ genCases!(US) ~ "}\n";
172     }
173     
174     string formatConsoleOutput( string message, Style level) @trusted
175     {
176         auto timeString = Clock.currTime.toISOExtString();
177         auto writer = appender!string();
178         
179         //pragma(msg, genSwitch!("level", 1, "message", "timeString", TS));
180         mixin(genSwitch!("level", 1, "message", "timeString", TS));
181         
182         return writer.data;
183     }
184     
185     string formatFileOutput( string message, Style level) @trusted
186     {
187         auto timeString = Clock.currTime.toISOExtString();
188         auto writer = appender!string();
189         
190         //pragma(msg, genSwitch!("level", 2, "message", "timeString", TS));
191         mixin(genSwitch!("level", 1, "message", "timeString", TS));
192         
193         return writer.data;
194     }
195 }
196 /// Example of default style
197 unittest
198 {
199     import dlogg.log;
200     mixin generateStyle!(LoggingLevel
201                 , LoggingLevel.Debug,   "Debug: %1$s",   "[%2$s] Debug: %1$s"
202                 , LoggingLevel.Notice,  "Notice: %1$s",  "[%2$s] Notice: %1$s"
203                 , LoggingLevel.Warning, "Warning: %1$s", "[%2$s] Warning: %1$s"
204                 , LoggingLevel.Fatal,   "Fatal: %1$s",   "[%2$s] Fatal: %1$s"
205                 , LoggingLevel.Muted,   "",              ""
206                 );
207 }
208 version(unittest)
209 {
210     enum MyLevel
211     {
212         Error,
213         Debug
214     }
215 }
216 /// Example of custom style
217 unittest
218 {    
219     mixin generateStyle!(MyLevel
220                 , MyLevel.Debug,   "Debug: %1$s",   "[%2$s] Debug: %1$s"
221                 , MyLevel.Error,   "Fatal: %1$s",   "[%2$s] Fatal: %1$s"
222                 );
223 }