log_logstash: send log messages to STDERR by default
[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 #if COLLECT_DEBUG
45 static int log_level = LOG_DEBUG;
46 #else
47 static int log_level = LOG_INFO;
48 #endif /* COLLECT_DEBUG */
49
50 static pthread_mutex_t file_lock = PTHREAD_MUTEX_INITIALIZER;
51
52 static char *log_file = NULL;
53
54 static const char *config_keys[] = {"LogLevel", "File"};
55 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
56
57 static int log_logstash_config(const char *key, const char *value) {
58
59   if (0 == strcasecmp(key, "LogLevel")) {
60     log_level = parse_log_severity(value);
61     if (log_level < 0) {
62       log_level = LOG_INFO;
63       ERROR("log_logstash: invalid loglevel [%s] defaulting to 'info'", value);
64       return 1;
65     }
66   } else if (0 == strcasecmp(key, "File")) {
67     sfree(log_file);
68     log_file = strdup(value);
69   } else {
70     return -1;
71   }
72   return 0;
73 } /* int log_logstash_config (const char *, const char *) */
74
75 static void log_logstash_print(yajl_gen g, int severity,
76                                cdtime_t timestamp_time) {
77   FILE *fh;
78   _Bool do_close = 0;
79   struct tm timestamp_tm;
80   char timestamp_str[64];
81   const unsigned char *buf;
82   time_t tt;
83 #if HAVE_YAJL_V2
84   size_t len;
85 #else
86   unsigned int len;
87 #endif
88
89   if (yajl_gen_string(g, (u_char *)"level", strlen("level")) !=
90       yajl_gen_status_ok)
91     goto err;
92
93   switch (severity) {
94   case LOG_ERR:
95     if (yajl_gen_string(g, (u_char *)"error", strlen("error")) !=
96         yajl_gen_status_ok)
97       goto err;
98     break;
99   case LOG_WARNING:
100     if (yajl_gen_string(g, (u_char *)"warning", strlen("warning")) !=
101         yajl_gen_status_ok)
102       goto err;
103     break;
104   case LOG_NOTICE:
105     if (yajl_gen_string(g, (u_char *)"notice", strlen("notice")) !=
106         yajl_gen_status_ok)
107       goto err;
108     break;
109   case LOG_INFO:
110     if (yajl_gen_string(g, (u_char *)"info", strlen("info")) !=
111         yajl_gen_status_ok)
112       goto err;
113     break;
114   case LOG_DEBUG:
115     if (yajl_gen_string(g, (u_char *)"debug", strlen("debug")) !=
116         yajl_gen_status_ok)
117       goto err;
118     break;
119   default:
120     if (yajl_gen_string(g, (u_char *)"unknown", strlen("unknown")) !=
121         yajl_gen_status_ok)
122       goto err;
123     break;
124   }
125
126   if (yajl_gen_string(g, (u_char *)"@timestamp", strlen("@timestamp")) !=
127       yajl_gen_status_ok)
128     goto err;
129
130   tt = CDTIME_T_TO_TIME_T(timestamp_time);
131   gmtime_r(&tt, &timestamp_tm);
132
133   /*
134    * format time as a UTC ISO 8601 compliant string
135    */
136   strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%dT%H:%M:%SZ",
137            &timestamp_tm);
138   timestamp_str[sizeof(timestamp_str) - 1] = '\0';
139
140   if (yajl_gen_string(g, (u_char *)timestamp_str, strlen(timestamp_str)) !=
141       yajl_gen_status_ok)
142     goto err;
143
144   if (yajl_gen_map_close(g) != yajl_gen_status_ok)
145     goto err;
146
147   if (yajl_gen_get_buf(g, &buf, &len) != yajl_gen_status_ok)
148     goto err;
149   pthread_mutex_lock(&file_lock);
150
151   if (log_file == NULL) {
152     fh = stderr;
153   } else if (strcasecmp(log_file, "stdout") == 0) {
154     fh = stdout;
155     do_close = 0;
156   } else if (strcasecmp(log_file, "stderr") == 0) {
157     fh = stderr;
158     do_close = 0;
159   } else {
160     fh = fopen(log_file, "a");
161     do_close = 1;
162   }
163
164   if (fh == NULL) {
165     char errbuf[1024];
166     fprintf(stderr, "log_logstash plugin: fopen (%s) failed: %s\n", log_file,
167             sstrerror(errno, errbuf, sizeof(errbuf)));
168   } else {
169     fprintf(fh, "%s\n", buf);
170     if (do_close) {
171       fclose(fh);
172     } else {
173       fflush(fh);
174     }
175   }
176   pthread_mutex_unlock(&file_lock);
177   yajl_gen_free(g);
178   return;
179
180 err:
181   yajl_gen_free(g);
182   fprintf(stderr, "Could not correctly generate JSON message\n");
183   return;
184 } /* void log_logstash_print */
185
186 static void log_logstash_log(int severity, const char *msg,
187                              user_data_t __attribute__((unused)) * user_data) {
188   yajl_gen g;
189 #if !defined(HAVE_YAJL_V2)
190   yajl_gen_config conf = {};
191
192   conf.beautify = 0;
193 #endif
194
195   if (severity > log_level)
196     return;
197
198 #if HAVE_YAJL_V2
199   g = yajl_gen_alloc(NULL);
200 #else
201   g = yajl_gen_alloc(&conf, NULL);
202 #endif
203
204   if (g == NULL) {
205     fprintf(stderr, "Could not allocate JSON generator.\n");
206     return;
207   }
208
209   if (yajl_gen_map_open(g) != yajl_gen_status_ok)
210     goto err;
211   if (yajl_gen_string(g, (u_char *)"message", strlen("message")) !=
212       yajl_gen_status_ok)
213     goto err;
214   if (yajl_gen_string(g, (u_char *)msg, strlen(msg)) != yajl_gen_status_ok)
215     goto err;
216
217   log_logstash_print(g, severity, cdtime());
218   return;
219 err:
220   yajl_gen_free(g);
221   fprintf(stderr, "Could not generate JSON message preamble\n");
222   return;
223
224 } /* void log_logstash_log (int, const char *) */
225
226 static int log_logstash_notification(const notification_t *n,
227                                      user_data_t __attribute__((unused)) *
228                                          user_data) {
229   yajl_gen g;
230 #if HAVE_YAJL_V2
231   g = yajl_gen_alloc(NULL);
232 #else
233   yajl_gen_config conf = {};
234
235   conf.beautify = 0;
236   g = yajl_gen_alloc(&conf, NULL);
237 #endif
238
239   if (g == NULL) {
240     fprintf(stderr, "Could not allocate JSON generator.\n");
241     return (0);
242   }
243
244   if (yajl_gen_map_open(g) != yajl_gen_status_ok)
245     goto err;
246   if (yajl_gen_string(g, (u_char *)"message", strlen("message")) !=
247       yajl_gen_status_ok)
248     goto err;
249   if (strlen(n->message) > 0) {
250     if (yajl_gen_string(g, (u_char *)n->message, strlen(n->message)) !=
251         yajl_gen_status_ok)
252       goto err;
253   } else {
254     if (yajl_gen_string(g, (u_char *)"notification without a message",
255                         strlen("notification without a message")) !=
256         yajl_gen_status_ok)
257       goto err;
258   }
259
260   if (strlen(n->host) > 0) {
261     if (yajl_gen_string(g, (u_char *)"host", strlen("host")) !=
262         yajl_gen_status_ok)
263       goto err;
264     if (yajl_gen_string(g, (u_char *)n->host, strlen(n->host)) !=
265         yajl_gen_status_ok)
266       goto err;
267   }
268   if (strlen(n->plugin) > 0) {
269     if (yajl_gen_string(g, (u_char *)"plugin", strlen("plugin")) !=
270         yajl_gen_status_ok)
271       goto err;
272     if (yajl_gen_string(g, (u_char *)n->plugin, strlen(n->plugin)) !=
273         yajl_gen_status_ok)
274       goto err;
275   }
276   if (strlen(n->plugin_instance) > 0) {
277     if (yajl_gen_string(g, (u_char *)"plugin_instance",
278                         strlen("plugin_instance")) != yajl_gen_status_ok)
279       goto err;
280     if (yajl_gen_string(g, (u_char *)n->plugin_instance,
281                         strlen(n->plugin_instance)) != yajl_gen_status_ok)
282       goto err;
283   }
284   if (strlen(n->type) > 0) {
285     if (yajl_gen_string(g, (u_char *)"type", strlen("type")) !=
286         yajl_gen_status_ok)
287       goto err;
288     if (yajl_gen_string(g, (u_char *)n->type, strlen(n->type)) !=
289         yajl_gen_status_ok)
290       goto err;
291   }
292   if (strlen(n->type_instance) > 0) {
293     if (yajl_gen_string(g, (u_char *)"type_instance",
294                         strlen("type_instance")) != yajl_gen_status_ok)
295       goto err;
296     if (yajl_gen_string(g, (u_char *)n->type_instance,
297                         strlen(n->type_instance)) != yajl_gen_status_ok)
298       goto err;
299   }
300
301   if (yajl_gen_string(g, (u_char *)"severity", strlen("severity")) !=
302       yajl_gen_status_ok)
303     goto err;
304
305   switch (n->severity) {
306   case NOTIF_FAILURE:
307     if (yajl_gen_string(g, (u_char *)"failure", strlen("failure")) !=
308         yajl_gen_status_ok)
309       goto err;
310     break;
311   case NOTIF_WARNING:
312     if (yajl_gen_string(g, (u_char *)"warning", strlen("warning")) !=
313         yajl_gen_status_ok)
314       goto err;
315     break;
316   case NOTIF_OKAY:
317     if (yajl_gen_string(g, (u_char *)"ok", strlen("ok")) != yajl_gen_status_ok)
318       goto err;
319     break;
320   default:
321     if (yajl_gen_string(g, (u_char *)"unknown", strlen("unknown")) !=
322         yajl_gen_status_ok)
323       goto err;
324     break;
325   }
326
327   log_logstash_print(g, LOG_INFO, (n->time != 0) ? n->time : cdtime());
328   return (0);
329
330 err:
331   yajl_gen_free(g);
332   fprintf(stderr, "Could not correctly generate JSON notification\n");
333   return (0);
334 } /* int log_logstash_notification */
335
336 void module_register(void) {
337   plugin_register_config("log_logstash", log_logstash_config, config_keys,
338                          config_keys_num);
339   plugin_register_log("log_logstash", log_logstash_log,
340                       /* user_data = */ NULL);
341   plugin_register_notification("log_logstash", log_logstash_notification,
342                                /* user_data = */ NULL);
343 } /* void module_register (void) */
344
345 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */