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 }