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