837796197a24387efa26e303405d61544db446b4
[collectd.git] / src / log_logstash.c
1 /**
2  * collectd - src/log_logstash.c
3  * Copyright (C) 2013       Pierre-Yves Ritschard
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Pierre-Yves Ritschard <pyr at spootnik.org>
25  * Acknowledgements:
26  *   This file is largely inspired by logfile.c
27  **/
28
29 #include "collectd.h"
30
31 #include "common.h"
32 #include "plugin.h"
33
34 #include <sys/types.h>
35 #include <yajl/yajl_common.h>
36 #include <yajl/yajl_gen.h>
37 #if HAVE_YAJL_YAJL_VERSION_H
38 #include <yajl/yajl_version.h>
39 #endif
40 #if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
41 #define HAVE_YAJL_V2 1
42 #endif
43
44 #define DEFAULT_LOGFILE LOCALSTATEDIR "/log/" PACKAGE_NAME ".json.log"
45
46 #if COLLECT_DEBUG
47 static int log_level = LOG_DEBUG;
48 #else
49 static int log_level = LOG_INFO;
50 #endif /* COLLECT_DEBUG */
51
52 static pthread_mutex_t file_lock = PTHREAD_MUTEX_INITIALIZER;
53
54 static char *log_file = NULL;
55
56 static const char *config_keys[] = {"LogLevel", "File"};
57 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
58
59 static int log_logstash_config(const char *key, const char *value) {
60
61   if (0 == strcasecmp(key, "LogLevel")) {
62     log_level = parse_log_severity(value);
63     if (log_level < 0) {
64       log_level = LOG_INFO;
65       ERROR("log_logstash: invalid loglevel [%s] defaulting to 'info'", value);
66       return 1;
67     }
68   } else if (0 == strcasecmp(key, "File")) {
69     sfree(log_file);
70     log_file = strdup(value);
71   } else {
72     return -1;
73   }
74   return 0;
75 } /* int log_logstash_config (const char *, const char *) */
76
77 static void log_logstash_print(yajl_gen g, int severity,
78                                cdtime_t timestamp_time) {
79   FILE *fh;
80   _Bool do_close = 0;
81   struct tm timestamp_tm;
82   char timestamp_str[64];
83   const unsigned char *buf;
84   time_t tt;
85 #if HAVE_YAJL_V2
86   size_t len;
87 #else
88   unsigned int len;
89 #endif
90
91   if (yajl_gen_string(g, (u_char *)"level", strlen("level")) !=
92       yajl_gen_status_ok)
93     goto err;
94
95   switch (severity) {
96   case LOG_ERR:
97     if (yajl_gen_string(g, (u_char *)"error", strlen("error")) !=
98         yajl_gen_status_ok)
99       goto err;
100     break;
101   case LOG_WARNING:
102     if (yajl_gen_string(g, (u_char *)"warning", strlen("warning")) !=
103         yajl_gen_status_ok)
104       goto err;
105     break;
106   case LOG_NOTICE:
107     if (yajl_gen_string(g, (u_char *)"notice", strlen("notice")) !=
108         yajl_gen_status_ok)
109       goto err;
110     break;
111   case LOG_INFO:
112     if (yajl_gen_string(g, (u_char *)"info", strlen("info")) !=
113         yajl_gen_status_ok)
114       goto err;
115     break;
116   case LOG_DEBUG:
117     if (yajl_gen_string(g, (u_char *)"debug", strlen("debug")) !=
118         yajl_gen_status_ok)
119       goto err;
120     break;
121   default:
122     if (yajl_gen_string(g, (u_char *)"unknown", strlen("unknown")) !=
123         yajl_gen_status_ok)
124       goto err;
125     break;
126   }
127
128   if (yajl_gen_string(g, (u_char *)"@timestamp", strlen("@timestamp")) !=
129       yajl_gen_status_ok)
130     goto err;
131
132   tt = CDTIME_T_TO_TIME_T(timestamp_time);
133   gmtime_r(&tt, &timestamp_tm);
134
135   /*
136    * format time as a UTC ISO 8601 compliant string
137    */
138   strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%dT%H:%M:%SZ",
139            &timestamp_tm);
140   timestamp_str[sizeof(timestamp_str) - 1] = '\0';
141
142   if (yajl_gen_string(g, (u_char *)timestamp_str, strlen(timestamp_str)) !=
143       yajl_gen_status_ok)
144     goto err;
145
146   if (yajl_gen_map_close(g) != yajl_gen_status_ok)
147     goto err;
148
149   if (yajl_gen_get_buf(g, &buf, &len) != yajl_gen_status_ok)
150     goto err;
151   pthread_mutex_lock(&file_lock);
152
153   if (log_file == NULL) {
154     fh = fopen(DEFAULT_LOGFILE, "a");
155     do_close = 1;
156   } else if (strcasecmp(log_file, "stdout") == 0) {
157     fh = stdout;
158     do_close = 0;
159   } else if (strcasecmp(log_file, "stderr") == 0) {
160     fh = stderr;
161     do_close = 0;
162   } else {
163     fh = fopen(log_file, "a");
164     do_close = 1;
165   }
166
167   if (fh == NULL) {
168     char errbuf[1024];
169     fprintf(stderr, "log_logstash plugin: fopen (%s) failed: %s\n",
170             (log_file == NULL) ? DEFAULT_LOGFILE : log_file,
171             sstrerror(errno, errbuf, sizeof(errbuf)));
172   } else {
173     fprintf(fh, "%s\n", buf);
174     if (do_close) {
175       fclose(fh);
176     } else {
177       fflush(fh);
178     }
179   }
180   pthread_mutex_unlock(&file_lock);
181   yajl_gen_free(g);
182   return;
183
184 err:
185   yajl_gen_free(g);
186   fprintf(stderr, "Could not correctly generate JSON message\n");
187   return;
188 } /* void log_logstash_print */
189
190 static void log_logstash_log(int severity, const char *msg,
191                              user_data_t __attribute__((unused)) * user_data) {
192   yajl_gen g;
193 #if !defined(HAVE_YAJL_V2)
194   yajl_gen_config conf = {};
195
196   conf.beautify = 0;
197 #endif
198
199   if (severity > log_level)
200     return;
201
202 #if HAVE_YAJL_V2
203   g = yajl_gen_alloc(NULL);
204 #else
205   g = yajl_gen_alloc(&conf, NULL);
206 #endif
207
208   if (g == NULL) {
209     fprintf(stderr, "Could not allocate JSON generator.\n");
210     return;
211   }
212
213   if (yajl_gen_map_open(g) != yajl_gen_status_ok)
214     goto err;
215   if (yajl_gen_string(g, (u_char *)"message", strlen("message")) !=
216       yajl_gen_status_ok)
217     goto err;
218   if (yajl_gen_string(g, (u_char *)msg, strlen(msg)) != yajl_gen_status_ok)
219     goto err;
220
221   log_logstash_print(g, severity, cdtime());
222   return;
223 err:
224   yajl_gen_free(g);
225   fprintf(stderr, "Could not generate JSON message preamble\n");
226   return;
227
228 } /* void log_logstash_log (int, const char *) */
229
230 static int log_logstash_notification(const notification_t *n,
231                                      user_data_t __attribute__((unused)) *
232                                          user_data) {
233   yajl_gen g;
234 #if HAVE_YAJL_V2
235   g = yajl_gen_alloc(NULL);
236 #else
237   yajl_gen_config conf = {};
238
239   conf.beautify = 0;
240   g = yajl_gen_alloc(&conf, NULL);
241 #endif
242
243   if (g == NULL) {
244     fprintf(stderr, "Could not allocate JSON generator.\n");
245     return (0);
246   }
247
248   if (yajl_gen_map_open(g) != yajl_gen_status_ok)
249     goto err;
250   if (yajl_gen_string(g, (u_char *)"message", strlen("message")) !=
251       yajl_gen_status_ok)
252     goto err;
253   if (strlen(n->message) > 0) {
254     if (yajl_gen_string(g, (u_char *)n->message, strlen(n->message)) !=
255         yajl_gen_status_ok)
256       goto err;
257   } else {
258     if (yajl_gen_string(g, (u_char *)"notification without a message",
259                         strlen("notification without a message")) !=
260         yajl_gen_status_ok)
261       goto err;
262   }
263
264   if (strlen(n->host) > 0) {
265     if (yajl_gen_string(g, (u_char *)"host", strlen("host")) !=
266         yajl_gen_status_ok)
267       goto err;
268     if (yajl_gen_string(g, (u_char *)n->host, strlen(n->host)) !=
269         yajl_gen_status_ok)
270       goto err;
271   }
272   if (strlen(n->plugin) > 0) {
273     if (yajl_gen_string(g, (u_char *)"plugin", strlen("plugin")) !=
274         yajl_gen_status_ok)
275       goto err;
276     if (yajl_gen_string(g, (u_char *)n->plugin, strlen(n->plugin)) !=
277         yajl_gen_status_ok)
278       goto err;
279   }
280   if (strlen(n->plugin_instance) > 0) {
281     if (yajl_gen_string(g, (u_char *)"plugin_instance",
282                         strlen("plugin_instance")) != yajl_gen_status_ok)
283       goto err;
284     if (yajl_gen_string(g, (u_char *)n->plugin_instance,
285                         strlen(n->plugin_instance)) != yajl_gen_status_ok)
286       goto err;
287   }
288   if (strlen(n->type) > 0) {
289     if (yajl_gen_string(g, (u_char *)"type", strlen("type")) !=
290         yajl_gen_status_ok)
291       goto err;
292     if (yajl_gen_string(g, (u_char *)n->type, strlen(n->type)) !=
293         yajl_gen_status_ok)
294       goto err;
295   }
296   if (strlen(n->type_instance) > 0) {
297     if (yajl_gen_string(g, (u_char *)"type_instance",
298                         strlen("type_instance")) != yajl_gen_status_ok)
299       goto err;
300     if (yajl_gen_string(g, (u_char *)n->type_instance,
301                         strlen(n->type_instance)) != yajl_gen_status_ok)
302       goto err;
303   }
304
305   if (yajl_gen_string(g, (u_char *)"severity", strlen("severity")) !=
306       yajl_gen_status_ok)
307     goto err;
308
309   switch (n->severity) {
310   case NOTIF_FAILURE:
311     if (yajl_gen_string(g, (u_char *)"failure", strlen("failure")) !=
312         yajl_gen_status_ok)
313       goto err;
314     break;
315   case NOTIF_WARNING:
316     if (yajl_gen_string(g, (u_char *)"warning", strlen("warning")) !=
317         yajl_gen_status_ok)
318       goto err;
319     break;
320   case NOTIF_OKAY:
321     if (yajl_gen_string(g, (u_char *)"ok", strlen("ok")) != yajl_gen_status_ok)
322       goto err;
323     break;
324   default:
325     if (yajl_gen_string(g, (u_char *)"unknown", strlen("unknown")) !=
326         yajl_gen_status_ok)
327       goto err;
328     break;
329   }
330
331   log_logstash_print(g, LOG_INFO, (n->time != 0) ? n->time : cdtime());
332   return (0);
333
334 err:
335   yajl_gen_free(g);
336   fprintf(stderr, "Could not correctly generate JSON notification\n");
337   return (0);
338 } /* int log_logstash_notification */
339
340 void module_register(void) {
341   plugin_register_config("log_logstash", log_logstash_config, config_keys,
342                          config_keys_num);
343   plugin_register_log("log_logstash", log_logstash_log,
344                       /* user_data = */ NULL);
345   plugin_register_notification("log_logstash", log_logstash_notification,
346                                /* user_data = */ NULL);
347 } /* void module_register (void) */
348
349 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */