1 module syslog.service; 2 3 import std.traits:isSomeString; 4 5 import vibe.d; 6 7 /// 8 static bool equalComponents(T,COMPONENTS...)(T _a, T _b) 9 { 10 foreach(comp; COMPONENTS) 11 { 12 static assert(is(typeof(comp) : string), "components must be strings"); 13 14 if(__traits(getMember, _a, comp) != __traits(getMember, _b, comp)) 15 return false; 16 } 17 18 return true; 19 } 20 21 unittest 22 { 23 struct Test{ 24 int i; 25 string s; 26 } 27 28 Test t1 = Test(12,"foo"); 29 Test t2 = Test(12,"bar"); 30 31 assert(equalComponents!(Test,"i")(t1,t2)); 32 assert(!equalComponents!(Test,"i","s")(t1,t2)); 33 } 34 35 /// 36 final class SysLogService 37 { 38 private: 39 /// 40 public alias RequestCallback = void delegate(string, in string[], HTTPServerRequest); 41 42 bool m_quiet; 43 ushort m_port = 8888; 44 string m_hostName = "hostUnknown"; 45 string m_logFolder = "./"; 46 string m_fileSuffix = ""; 47 bool m_dashesInFileNameTimeStamp = false; 48 bool m_oneLogPerHour = false; 49 RequestCallback m_requestModifierCallback; 50 51 SysTime m_lastFileNameUpdateTime = SysTime.min; 52 string m_lastFileName; 53 54 static immutable FORMATSTR_DAILY = "%04d-%02d-%02d_%s%s.log"; 55 static immutable FORMATSTR_DAILY_SHORT = "%04d%02d%02d%s%s.log"; 56 static immutable FORMATSTR_HOURLY = "%04d-%02d-%02d-%02d00_%s%s.log"; 57 static immutable FORMATSTR_HOURLY_SHORT = "%04d%02d%02d%02d00_%s%s.log"; 58 59 /// 60 @property public void port(ushort _port) { m_port = _port; } 61 /// 62 @property public void quiet(bool _quiet) { m_quiet = _quiet; } 63 /// 64 @property public void hostName(string _hostname) { m_hostName = _hostname; } 65 /// 66 @property public void fileSuffix(string _suffix) { m_fileSuffix = _suffix.length>0 ? "_"~_suffix : ""; } 67 /// 68 @property public void oneLogPerHour(bool _value) { m_oneLogPerHour = _value; } 69 /// 70 @property public void enableDashesInTimeStampOfLogFiles(bool _value) { m_dashesInFileNameTimeStamp = _value; } 71 /// 72 @property public void logFolder(string _logFolder) { m_logFolder = _logFolder; } 73 /// 74 @property public void requestModifierCallback(RequestCallback _cb) { m_requestModifierCallback = _cb; } 75 76 /++ 77 + starts the http server. 78 + If not changed the default listens on port 8888, 79 + is quiet (does not print every log msg to the stdout), 80 + is named "hostUnknown" and 81 + writes the logfiles to "./". 82 +/ 83 public void start() 84 { 85 auto settings = new HTTPServerSettings(); 86 settings.port = m_port; 87 settings.options = HTTPServerOption.parseFormBody; 88 settings.bindAddresses = ["0.0.0.0"]; 89 90 listenHTTP(settings, &handleRequest); 91 } 92 93 /// 94 void handleRequest(HTTPServerRequest req, HTTPServerResponse res) 95 { 96 res.statusCode = 200; 97 res.headers["Content-Length"]="0"; 98 res.writeBody(""); 99 100 if(req.requestURL.startsWith("/")) 101 req.requestURL = req.requestURL[1..$]; 102 103 auto eventNames = req.requestURL.split("/"); 104 105 if(eventNames.length == 0 || eventNames[$-1].length == 0) 106 { 107 logError("req has no event set: %s",req.requestURL); 108 return; 109 } 110 111 auto event = eventNames[$-1]; 112 113 //note: ignore this default request 114 if(event == "favicon.ico") 115 return; 116 117 if(m_requestModifierCallback) 118 m_requestModifierCallback(event, eventNames[0..$-1], req); 119 120 syslog(eventNames, req.form, req.peer, req.clientAddress.port); 121 } 122 123 /// 124 void syslog(string[] _events, FormFields _values, string _ip, ushort _port) 125 { 126 auto logline = createSyslogLine(_events, _values); 127 128 if(!m_quiet) 129 logInfo("%s",logline); 130 131 appendToFile(m_logFolder ~ getLogFileName(), logline); 132 } 133 134 /// 135 string getLogFileName() 136 { 137 auto currentTime = Clock.currTime(); 138 139 if(!m_oneLogPerHour) 140 { 141 if(!equalComponents!(SysTime,"year","month","day")(currentTime,m_lastFileNameUpdateTime)) 142 { 143 m_lastFileName = format(m_dashesInFileNameTimeStamp?FORMATSTR_DAILY:FORMATSTR_DAILY_SHORT, 144 currentTime.year, 145 currentTime.month, 146 currentTime.day, 147 m_hostName, 148 m_fileSuffix); 149 150 m_lastFileNameUpdateTime = currentTime; 151 } 152 } 153 else 154 { 155 if(!equalComponents!(SysTime,"year","month","day","hour")(currentTime,m_lastFileNameUpdateTime)) 156 { 157 m_lastFileName = format(m_dashesInFileNameTimeStamp?FORMATSTR_HOURLY:FORMATSTR_HOURLY_SHORT, 158 currentTime.year, 159 currentTime.month, 160 currentTime.day, 161 currentTime.hour, 162 m_hostName, 163 m_fileSuffix); 164 165 m_lastFileNameUpdateTime = currentTime; 166 } 167 } 168 169 return m_lastFileName; 170 } 171 172 /// 173 string getLogLineDate() 174 { 175 auto currentTime = Clock.currTime(); 176 177 enum monthNames = [ 178 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 179 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]; 180 181 //Sep 11 00:02:14 182 return format("%s %02d %02d:%02d:%02d", 183 monthNames[currentTime.month-1], 184 currentTime.day, 185 currentTime.hour, 186 currentTime.minute, 187 currentTime.second); 188 } 189 190 /// 191 string createSyslogLine(string[] _events, FormFields _values) 192 { 193 import std.array; 194 195 Appender!string line; 196 197 line.put(getLogLineDate()); 198 199 line ~= " "; 200 line ~= m_hostName; 201 202 foreach(e; _events[0..$-1]) 203 { 204 line ~= " "; 205 line ~= e; 206 } 207 208 line ~= " - "; 209 line ~= _events[$-1]; 210 211 if(_values.length > 0) 212 { 213 line ~= " ["; 214 215 foreach(k, v; _values) 216 { 217 line ~= k; 218 line ~= "=\""; 219 line ~= v; 220 line ~= "\" "; 221 } 222 223 line ~= "]"; 224 } 225 226 line ~= "\n"; 227 228 return line.data; 229 } 230 }