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 }