/src/fluent-bit/plugins/out_logdna/logdna.c
Line | Count | Source |
1 | | /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* Fluent Bit |
4 | | * ========== |
5 | | * Copyright (C) 2015-2026 The Fluent Bit Authors |
6 | | * |
7 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
8 | | * you may not use this file except in compliance with the License. |
9 | | * You may obtain a copy of the License at |
10 | | * |
11 | | * http://www.apache.org/licenses/LICENSE-2.0 |
12 | | * |
13 | | * Unless required by applicable law or agreed to in writing, software |
14 | | * distributed under the License is distributed on an "AS IS" BASIS, |
15 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | | * See the License for the specific language governing permissions and |
17 | | * limitations under the License. |
18 | | */ |
19 | | |
20 | | #include <fluent-bit/flb_output_plugin.h> |
21 | | #include <fluent-bit/flb_mp.h> |
22 | | #include <fluent-bit/flb_pack.h> |
23 | | #include <fluent-bit/flb_env.h> |
24 | | #include <fluent-bit/flb_http_client.h> |
25 | | #include <fluent-bit/flb_log_event_decoder.h> |
26 | | |
27 | | #include "logdna.h" |
28 | | |
29 | 0 | #define LOGDNA_META_KEY "meta" |
30 | 0 | #define LOGDNA_LEVEL_KEY "level" |
31 | 0 | #define LOGDNA_SEVERITY_KEY "severity" |
32 | 0 | #define LOGDNA_FILE_KEY "file" |
33 | 0 | #define LOGDNA_APP_KEY "app" |
34 | | |
35 | | static inline int primary_key_check(msgpack_object k, char *name, int len) |
36 | 0 | { |
37 | 0 | if (k.type != MSGPACK_OBJECT_STR) { |
38 | 0 | return FLB_FALSE; |
39 | 0 | } |
40 | | |
41 | 0 | if (k.via.str.size != len) { |
42 | 0 | return FLB_FALSE; |
43 | 0 | } |
44 | | |
45 | 0 | if (memcmp(k.via.str.ptr, name, len) == 0) { |
46 | 0 | return FLB_TRUE; |
47 | 0 | } |
48 | | |
49 | 0 | return FLB_FALSE; |
50 | 0 | } |
51 | | |
52 | | /* |
53 | | * This function looks for the following primary keys and promotes them to |
54 | | * the top-level line object: |
55 | | * |
56 | | * - level or severity |
57 | | * - file |
58 | | * - app |
59 | | * - meta |
60 | | * |
61 | | * When line_pck is not NULL, non-primary keys are packed into it for use |
62 | | * as the "line" body (excluding the promoted keys). |
63 | | */ |
64 | | static int record_append_primary_keys(struct flb_logdna *ctx, |
65 | | msgpack_object *map, |
66 | | msgpack_packer *mp_sbuf, |
67 | | msgpack_packer *line_pck) |
68 | 0 | { |
69 | 0 | int i; |
70 | 0 | int c = 0; |
71 | 0 | int is_primary; |
72 | 0 | int line_count = 0; |
73 | 0 | msgpack_object *level = NULL; |
74 | 0 | msgpack_object *file = NULL; |
75 | 0 | msgpack_object *app = NULL; |
76 | 0 | msgpack_object *meta = NULL; |
77 | 0 | msgpack_object k; |
78 | 0 | msgpack_object v; |
79 | |
|
80 | 0 | if (line_pck) { |
81 | 0 | for (i = 0; i < map->via.map.size; i++) { |
82 | 0 | k = map->via.map.ptr[i].key; |
83 | 0 | if (primary_key_check(k, LOGDNA_META_KEY, sizeof(LOGDNA_META_KEY) - 1) == FLB_TRUE || |
84 | 0 | primary_key_check(k, LOGDNA_LEVEL_KEY, sizeof(LOGDNA_LEVEL_KEY) - 1) == FLB_TRUE || |
85 | 0 | primary_key_check(k, LOGDNA_SEVERITY_KEY, sizeof(LOGDNA_SEVERITY_KEY) - 1) == FLB_TRUE || |
86 | 0 | primary_key_check(k, LOGDNA_FILE_KEY, sizeof(LOGDNA_FILE_KEY) - 1) == FLB_TRUE || |
87 | 0 | primary_key_check(k, LOGDNA_APP_KEY, sizeof(LOGDNA_APP_KEY) - 1) == FLB_TRUE) { |
88 | 0 | continue; |
89 | 0 | } |
90 | 0 | line_count++; |
91 | 0 | } |
92 | 0 | msgpack_pack_map(line_pck, line_count); |
93 | 0 | } |
94 | |
|
95 | 0 | for (i = 0; i < map->via.map.size; i++) { |
96 | 0 | k = map->via.map.ptr[i].key; |
97 | 0 | v = map->via.map.ptr[i].val; |
98 | 0 | is_primary = FLB_FALSE; |
99 | | |
100 | | /* Level - optional (both "level" and "severity" are primary) */ |
101 | 0 | if (primary_key_check(k, LOGDNA_LEVEL_KEY, sizeof(LOGDNA_LEVEL_KEY) - 1) == FLB_TRUE || |
102 | 0 | primary_key_check(k, LOGDNA_SEVERITY_KEY, sizeof(LOGDNA_SEVERITY_KEY) - 1) == FLB_TRUE) { |
103 | 0 | is_primary = FLB_TRUE; |
104 | 0 | if (!level) { |
105 | 0 | level = &k; |
106 | 0 | msgpack_pack_str(mp_sbuf, sizeof(LOGDNA_LEVEL_KEY) - 1); |
107 | 0 | msgpack_pack_str_body(mp_sbuf, LOGDNA_LEVEL_KEY, sizeof(LOGDNA_LEVEL_KEY) - 1); |
108 | 0 | msgpack_pack_object(mp_sbuf, v); |
109 | 0 | c++; |
110 | 0 | } |
111 | 0 | } |
112 | | |
113 | | /* Meta - optional */ |
114 | 0 | if (primary_key_check(k, LOGDNA_META_KEY, sizeof(LOGDNA_META_KEY) - 1) == FLB_TRUE) { |
115 | 0 | is_primary = FLB_TRUE; |
116 | 0 | if (!meta) { |
117 | 0 | meta = &k; |
118 | 0 | msgpack_pack_str(mp_sbuf, sizeof(LOGDNA_META_KEY) - 1); |
119 | 0 | msgpack_pack_str_body(mp_sbuf, LOGDNA_META_KEY, sizeof(LOGDNA_META_KEY) - 1); |
120 | 0 | msgpack_pack_object(mp_sbuf, v); |
121 | 0 | c++; |
122 | 0 | } |
123 | 0 | } |
124 | | |
125 | | /* File */ |
126 | 0 | if (primary_key_check(k, LOGDNA_FILE_KEY, sizeof(LOGDNA_FILE_KEY) - 1) == FLB_TRUE) { |
127 | 0 | is_primary = FLB_TRUE; |
128 | 0 | if (!file) { |
129 | 0 | file = &k; |
130 | 0 | msgpack_pack_str(mp_sbuf, sizeof(LOGDNA_FILE_KEY) - 1); |
131 | 0 | msgpack_pack_str_body(mp_sbuf, LOGDNA_FILE_KEY, sizeof(LOGDNA_FILE_KEY) - 1); |
132 | 0 | msgpack_pack_object(mp_sbuf, v); |
133 | 0 | c++; |
134 | 0 | } |
135 | 0 | } |
136 | | |
137 | | /* App */ |
138 | 0 | if (primary_key_check(k, LOGDNA_APP_KEY, sizeof(LOGDNA_APP_KEY) - 1) == FLB_TRUE) { |
139 | 0 | is_primary = FLB_TRUE; |
140 | 0 | if (!app) { |
141 | 0 | app = &k; |
142 | 0 | msgpack_pack_str(mp_sbuf, sizeof(LOGDNA_APP_KEY) - 1); |
143 | 0 | msgpack_pack_str_body(mp_sbuf, LOGDNA_APP_KEY, sizeof(LOGDNA_APP_KEY) - 1); |
144 | 0 | msgpack_pack_object(mp_sbuf, v); |
145 | 0 | c++; |
146 | 0 | } |
147 | 0 | } |
148 | |
|
149 | 0 | if (line_pck && is_primary == FLB_FALSE) { |
150 | 0 | msgpack_pack_object(line_pck, k); |
151 | 0 | msgpack_pack_object(line_pck, v); |
152 | 0 | } |
153 | 0 | } |
154 | | |
155 | | /* Set the global file name if the record did not provided one */ |
156 | 0 | if (!file && ctx->file) { |
157 | 0 | msgpack_pack_str(mp_sbuf, sizeof(LOGDNA_FILE_KEY) - 1); |
158 | 0 | msgpack_pack_str_body(mp_sbuf, LOGDNA_FILE_KEY, sizeof(LOGDNA_FILE_KEY) - 1); |
159 | 0 | msgpack_pack_str(mp_sbuf, flb_sds_len(ctx->file)); |
160 | 0 | msgpack_pack_str_body(mp_sbuf, ctx->file, flb_sds_len(ctx->file)); |
161 | 0 | c++; |
162 | 0 | } |
163 | | |
164 | | /* If no application name is set, set the default */ |
165 | 0 | if (!app) { |
166 | 0 | msgpack_pack_str(mp_sbuf, sizeof(LOGDNA_APP_KEY) - 1); |
167 | 0 | msgpack_pack_str_body(mp_sbuf, LOGDNA_APP_KEY, sizeof(LOGDNA_APP_KEY) - 1); |
168 | 0 | msgpack_pack_str(mp_sbuf, flb_sds_len(ctx->app)); |
169 | 0 | msgpack_pack_str_body(mp_sbuf, ctx->app, flb_sds_len(ctx->app)); |
170 | 0 | c++; |
171 | 0 | } |
172 | |
|
173 | 0 | return c; |
174 | 0 | } |
175 | | |
176 | | static flb_sds_t logdna_compose_payload(struct flb_logdna *ctx, |
177 | | const void *data, size_t bytes, |
178 | | const char *tag, int tag_len, |
179 | | struct flb_config *config) |
180 | 0 | { |
181 | 0 | int ret; |
182 | 0 | int len; |
183 | 0 | int total_lines; |
184 | 0 | int array_size = 0; |
185 | 0 | off_t map_off; |
186 | 0 | size_t off; |
187 | 0 | char *line_json; |
188 | 0 | flb_sds_t json; |
189 | 0 | msgpack_packer mp_pck; |
190 | 0 | msgpack_sbuffer mp_sbuf; |
191 | 0 | msgpack_packer mp_line_pck; |
192 | 0 | msgpack_sbuffer mp_line_sbuf; |
193 | 0 | msgpack_unpacked mp_line_result; |
194 | 0 | struct flb_log_event_decoder log_decoder; |
195 | 0 | struct flb_log_event log_event; |
196 | |
|
197 | 0 | ret = flb_log_event_decoder_init(&log_decoder, (char *) data, bytes); |
198 | |
|
199 | 0 | if (ret != FLB_EVENT_DECODER_SUCCESS) { |
200 | 0 | flb_plg_error(ctx->ins, |
201 | 0 | "Log event decoder initialization error : %d", ret); |
202 | |
|
203 | 0 | return NULL; |
204 | 0 | } |
205 | | |
206 | | /* Count number of records */ |
207 | 0 | total_lines = flb_mp_count_log_records(data, bytes); |
208 | | |
209 | | /* Initialize msgpack buffers */ |
210 | 0 | msgpack_sbuffer_init(&mp_sbuf); |
211 | 0 | msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write); |
212 | |
|
213 | 0 | msgpack_pack_map(&mp_pck, 1); |
214 | |
|
215 | 0 | msgpack_pack_str(&mp_pck, 5); |
216 | 0 | msgpack_pack_str_body(&mp_pck, "lines", 5); |
217 | |
|
218 | 0 | msgpack_pack_array(&mp_pck, total_lines); |
219 | |
|
220 | 0 | while ((ret = flb_log_event_decoder_next( |
221 | 0 | &log_decoder, |
222 | 0 | &log_event)) == FLB_EVENT_DECODER_SUCCESS) { |
223 | 0 | map_off = mp_sbuf.size; |
224 | |
|
225 | 0 | array_size = 2; |
226 | 0 | msgpack_pack_map(&mp_pck, array_size); |
227 | | |
228 | | /* |
229 | | * Append primary keys found, the return value is the number of appended |
230 | | * keys to the record, we use that to adjust the map header size. |
231 | | * |
232 | | * When exclude_promoted_keys is enabled, non-primary keys are packed |
233 | | * into mp_line_sbuf for use as the "line" body. |
234 | | */ |
235 | 0 | if (ctx->exclude_promoted_keys) { |
236 | 0 | msgpack_sbuffer_init(&mp_line_sbuf); |
237 | 0 | msgpack_packer_init(&mp_line_pck, &mp_line_sbuf, |
238 | 0 | msgpack_sbuffer_write); |
239 | |
|
240 | 0 | ret = record_append_primary_keys(ctx, log_event.body, |
241 | 0 | &mp_pck, &mp_line_pck); |
242 | 0 | } |
243 | 0 | else { |
244 | 0 | ret = record_append_primary_keys(ctx, log_event.body, |
245 | 0 | &mp_pck, NULL); |
246 | 0 | } |
247 | 0 | array_size += ret; |
248 | | |
249 | | /* Timestamp */ |
250 | 0 | msgpack_pack_str(&mp_pck, 9); |
251 | 0 | msgpack_pack_str_body(&mp_pck, "timestamp", 9); |
252 | 0 | msgpack_pack_int(&mp_pck, (int) flb_time_to_double(&log_event.timestamp)); |
253 | | |
254 | | /* Line */ |
255 | 0 | msgpack_pack_str(&mp_pck, 4); |
256 | 0 | msgpack_pack_str_body(&mp_pck, "line", 4); |
257 | |
|
258 | 0 | if (ctx->exclude_promoted_keys) { |
259 | 0 | msgpack_unpacked_init(&mp_line_result); |
260 | 0 | off = 0; |
261 | 0 | msgpack_unpack_next(&mp_line_result, |
262 | 0 | mp_line_sbuf.data, mp_line_sbuf.size, &off); |
263 | |
|
264 | 0 | line_json = flb_msgpack_to_json_str(1024, &mp_line_result.data, |
265 | 0 | config->json_escape_unicode); |
266 | |
|
267 | 0 | msgpack_unpacked_destroy(&mp_line_result); |
268 | 0 | msgpack_sbuffer_destroy(&mp_line_sbuf); |
269 | 0 | } |
270 | 0 | else { |
271 | 0 | line_json = flb_msgpack_to_json_str(1024, log_event.body, |
272 | 0 | config->json_escape_unicode); |
273 | 0 | } |
274 | |
|
275 | 0 | len = strlen(line_json); |
276 | 0 | msgpack_pack_str(&mp_pck, len); |
277 | 0 | msgpack_pack_str_body(&mp_pck, line_json, len); |
278 | 0 | flb_free(line_json); |
279 | | |
280 | | /* Adjust map header size */ |
281 | 0 | flb_mp_set_map_header_size(mp_sbuf.data + map_off, array_size); |
282 | 0 | } |
283 | |
|
284 | 0 | flb_log_event_decoder_destroy(&log_decoder); |
285 | |
|
286 | 0 | json = flb_msgpack_raw_to_json_sds(mp_sbuf.data, mp_sbuf.size, |
287 | 0 | config->json_escape_unicode); |
288 | 0 | msgpack_sbuffer_destroy(&mp_sbuf); |
289 | |
|
290 | 0 | return json; |
291 | 0 | } |
292 | | |
293 | | static void logdna_config_destroy(struct flb_logdna *ctx) |
294 | 0 | { |
295 | 0 | if (ctx->u) { |
296 | 0 | flb_upstream_destroy(ctx->u); |
297 | 0 | } |
298 | |
|
299 | 0 | if (ctx->tags_formatted) { |
300 | 0 | flb_sds_destroy(ctx->tags_formatted); |
301 | 0 | } |
302 | |
|
303 | 0 | flb_free(ctx); |
304 | 0 | } |
305 | | |
306 | | static struct flb_logdna *logdna_config_create(struct flb_output_instance *ins, |
307 | | struct flb_config *config) |
308 | 0 | { |
309 | 0 | int ret; |
310 | 0 | int len = 0; |
311 | 0 | char *hostname; |
312 | 0 | flb_sds_t tmp; |
313 | 0 | flb_sds_t encoded; |
314 | 0 | struct mk_list *head; |
315 | 0 | struct flb_slist_entry *tag_entry; |
316 | 0 | struct flb_logdna *ctx; |
317 | 0 | struct flb_upstream *upstream; |
318 | | |
319 | | /* Create context */ |
320 | 0 | ctx = flb_calloc(1, sizeof(struct flb_logdna)); |
321 | 0 | if (!ctx) { |
322 | 0 | flb_errno(); |
323 | 0 | return NULL; |
324 | 0 | } |
325 | 0 | ctx->ins = ins; |
326 | | |
327 | | /* Load config map */ |
328 | 0 | ret = flb_output_config_map_set(ins, (void *) ctx); |
329 | 0 | if (ret == -1) { |
330 | 0 | logdna_config_destroy(ctx); |
331 | 0 | return NULL; |
332 | 0 | } |
333 | | |
334 | | /* validate API key */ |
335 | 0 | if (!ctx->api_key) { |
336 | 0 | flb_plg_error(ins, "no `api_key` was set, this is a mandatory property"); |
337 | 0 | logdna_config_destroy(ctx); |
338 | 0 | return NULL; |
339 | 0 | } |
340 | | |
341 | | /* |
342 | | * Tags: this value is a linked list of values created by the config map |
343 | | * reader. |
344 | | */ |
345 | 0 | if (ctx->tags) { |
346 | | /* For every tag, make sure no empty spaces exists */ |
347 | 0 | mk_list_foreach(head, ctx->tags) { |
348 | 0 | tag_entry = mk_list_entry(head, struct flb_slist_entry, _head); |
349 | 0 | len += flb_sds_len(tag_entry->str) + 1; |
350 | 0 | } |
351 | | |
352 | | /* Compose a full tag for URI request */ |
353 | 0 | ctx->tags_formatted = flb_sds_create_size(len); |
354 | 0 | if (!ctx->tags_formatted) { |
355 | 0 | logdna_config_destroy(ctx); |
356 | 0 | return NULL; |
357 | 0 | } |
358 | | |
359 | 0 | mk_list_foreach(head, ctx->tags) { |
360 | 0 | tag_entry = mk_list_entry(head, struct flb_slist_entry, _head); |
361 | |
|
362 | 0 | encoded = flb_uri_encode(tag_entry->str, |
363 | 0 | flb_sds_len(tag_entry->str)); |
364 | 0 | tmp = flb_sds_cat(ctx->tags_formatted, |
365 | 0 | encoded, flb_sds_len(encoded)); |
366 | 0 | ctx->tags_formatted = tmp; |
367 | 0 | flb_sds_destroy(encoded); |
368 | |
|
369 | 0 | if (tag_entry->_head.next != ctx->tags) { |
370 | 0 | tmp = flb_sds_cat(ctx->tags_formatted, ",", 1); |
371 | 0 | ctx->tags_formatted = tmp; |
372 | 0 | } |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | | /* |
377 | | * Hostname: if the hostname was not set manually, try to get it from the |
378 | | * environment variable. |
379 | | * |
380 | | * Note that hostname is populated by a config map, and config maps are |
381 | | * immutable so we use an internal variable to do a final composition |
382 | | * if required. |
383 | | */ |
384 | 0 | if (!ctx->hostname) { |
385 | 0 | tmp = NULL; |
386 | 0 | hostname = (char *) flb_env_get(config->env, "HOSTNAME"); |
387 | 0 | if (hostname) { |
388 | 0 | ctx->_hostname = flb_sds_create(hostname); |
389 | 0 | } |
390 | 0 | else { |
391 | 0 | ctx->_hostname = flb_sds_create("unknown"); |
392 | 0 | } |
393 | 0 | } |
394 | 0 | else { |
395 | 0 | ctx->_hostname = flb_sds_create(ctx->hostname); |
396 | 0 | } |
397 | | |
398 | | /* Bail if unsuccessful hostname creation */ |
399 | 0 | if (!ctx->_hostname) { |
400 | 0 | flb_free(ctx); |
401 | 0 | return NULL; |
402 | 0 | } |
403 | | |
404 | | /* Create Upstream connection context */ |
405 | 0 | upstream = flb_upstream_create(config, |
406 | 0 | ctx->logdna_host, |
407 | 0 | ctx->logdna_port, |
408 | 0 | FLB_IO_TLS, ins->tls); |
409 | 0 | if (!upstream) { |
410 | 0 | flb_free(ctx); |
411 | 0 | return NULL; |
412 | 0 | } |
413 | 0 | ctx->u = upstream; |
414 | 0 | flb_output_upstream_set(ctx->u, ins); |
415 | | |
416 | | /* Set networking defaults */ |
417 | 0 | flb_output_net_default(FLB_LOGDNA_HOST, atoi(FLB_LOGDNA_PORT), ins); |
418 | 0 | return ctx; |
419 | 0 | } |
420 | | |
421 | | static int cb_logdna_init(struct flb_output_instance *ins, |
422 | | struct flb_config *config, void *data) |
423 | 0 | { |
424 | 0 | struct flb_logdna *ctx; |
425 | |
|
426 | 0 | ctx = logdna_config_create(ins, config); |
427 | 0 | if (!ctx) { |
428 | 0 | flb_plg_error(ins, "cannot initialize configuration"); |
429 | 0 | return -1; |
430 | 0 | } |
431 | | |
432 | 0 | flb_output_set_context(ins, ctx); |
433 | | |
434 | | /* |
435 | | * This plugin instance uses the HTTP client interface, let's register |
436 | | * it debugging callbacks. |
437 | | */ |
438 | 0 | flb_output_set_http_debug_callbacks(ins); |
439 | |
|
440 | 0 | flb_plg_info(ins, "configured, hostname=%s", ctx->hostname); |
441 | 0 | return 0; |
442 | 0 | } |
443 | | |
444 | | static void cb_logdna_flush(struct flb_event_chunk *event_chunk, |
445 | | struct flb_output_flush *out_flush, |
446 | | struct flb_input_instance *i_ins, |
447 | | void *out_context, |
448 | | struct flb_config *config) |
449 | 0 | { |
450 | 0 | int ret; |
451 | 0 | int out_ret = FLB_OK; |
452 | 0 | size_t b_sent; |
453 | 0 | flb_sds_t uri; |
454 | 0 | flb_sds_t tmp; |
455 | 0 | flb_sds_t payload; |
456 | 0 | struct flb_logdna *ctx = out_context; |
457 | 0 | struct flb_connection *u_conn; |
458 | 0 | struct flb_http_client *c; |
459 | | |
460 | | /* Format the data to the expected LogDNA Payload */ |
461 | 0 | payload = logdna_compose_payload(ctx, |
462 | 0 | event_chunk->data, |
463 | 0 | event_chunk->size, |
464 | 0 | event_chunk->tag, |
465 | 0 | flb_sds_len(event_chunk->tag), |
466 | 0 | config); |
467 | 0 | if (!payload) { |
468 | 0 | flb_plg_error(ctx->ins, "cannot compose request payload"); |
469 | 0 | FLB_OUTPUT_RETURN(FLB_RETRY); |
470 | 0 | } |
471 | | |
472 | | /* Lookup an available connection context */ |
473 | 0 | u_conn = flb_upstream_conn_get(ctx->u); |
474 | 0 | if (!u_conn) { |
475 | 0 | flb_plg_error(ctx->ins, "no upstream connections available"); |
476 | 0 | flb_sds_destroy(payload); |
477 | 0 | FLB_OUTPUT_RETURN(FLB_RETRY); |
478 | 0 | } |
479 | | |
480 | | /* Compose the HTTP URI */ |
481 | 0 | uri = flb_sds_create_size(256); |
482 | 0 | if (!uri) { |
483 | 0 | flb_plg_error(ctx->ins, "cannot allocate buffer for URI"); |
484 | 0 | flb_sds_destroy(payload); |
485 | 0 | flb_free(ctx); |
486 | 0 | FLB_OUTPUT_RETURN(FLB_RETRY); |
487 | 0 | } |
488 | 0 | tmp = flb_sds_printf(&uri, |
489 | 0 | "%s?hostname=%s&mac=%s&ip=%s&now=%lu&tags=%s", |
490 | 0 | ctx->logdna_endpoint, |
491 | 0 | ctx->_hostname, |
492 | 0 | ctx->mac_addr, |
493 | 0 | ctx->ip_addr, |
494 | 0 | time(NULL), |
495 | 0 | ctx->tags_formatted); |
496 | 0 | if (!tmp) { |
497 | 0 | flb_plg_error(ctx->ins, "error formatting URI"); |
498 | 0 | flb_sds_destroy(payload); |
499 | 0 | flb_free(ctx); |
500 | 0 | FLB_OUTPUT_RETURN(FLB_RETRY); |
501 | 0 | } |
502 | | |
503 | | /* Create HTTP client context */ |
504 | 0 | c = flb_http_client(u_conn, FLB_HTTP_POST, uri, |
505 | 0 | payload, flb_sds_len(payload), |
506 | 0 | ctx->logdna_host, ctx->logdna_port, |
507 | 0 | NULL, 0); |
508 | 0 | if (!c) { |
509 | 0 | flb_plg_error(ctx->ins, "cannot create HTTP client context"); |
510 | 0 | flb_sds_destroy(uri); |
511 | 0 | flb_sds_destroy(payload); |
512 | 0 | flb_upstream_conn_release(u_conn); |
513 | 0 | FLB_OUTPUT_RETURN(FLB_RETRY); |
514 | 0 | } |
515 | | |
516 | | /* Set callback context to the HTTP client context */ |
517 | 0 | flb_http_set_callback_context(c, ctx->ins->callback); |
518 | | |
519 | | /* User Agent */ |
520 | 0 | flb_http_add_header(c, "User-Agent", 10, "Fluent-Bit", 10); |
521 | | |
522 | | /* Add Content-Type header */ |
523 | 0 | flb_http_add_header(c, |
524 | 0 | FLB_LOGDNA_CT, sizeof(FLB_LOGDNA_CT) - 1, |
525 | 0 | FLB_LOGDNA_CT_JSON, sizeof(FLB_LOGDNA_CT_JSON) - 1); |
526 | | |
527 | | /* Add auth */ |
528 | 0 | flb_http_basic_auth(c, ctx->api_key, ""); |
529 | |
|
530 | 0 | flb_http_strip_port_from_host(c); |
531 | | |
532 | | /* Send HTTP request */ |
533 | 0 | ret = flb_http_do(c, &b_sent); |
534 | | |
535 | | /* Destroy buffers */ |
536 | 0 | flb_sds_destroy(uri); |
537 | 0 | flb_sds_destroy(payload); |
538 | | |
539 | | /* Validate HTTP client return status */ |
540 | 0 | if (ret == 0) { |
541 | | /* |
542 | | * Only allow the following HTTP status: |
543 | | * |
544 | | * - 200: OK |
545 | | * - 201: Created |
546 | | * - 202: Accepted |
547 | | * - 203: no authorative resp |
548 | | * - 204: No Content |
549 | | * - 205: Reset content |
550 | | * |
551 | | */ |
552 | 0 | if (c->resp.status < 200 || c->resp.status > 205) { |
553 | 0 | if (c->resp.payload) { |
554 | 0 | flb_plg_error(ctx->ins, "%s:%i, HTTP status=%i\n%s", |
555 | 0 | ctx->logdna_host, ctx->logdna_port, c->resp.status, |
556 | 0 | c->resp.payload); |
557 | 0 | } |
558 | 0 | else { |
559 | 0 | flb_plg_error(ctx->ins, "%s:%i, HTTP status=%i", |
560 | 0 | ctx->logdna_host, ctx->logdna_port, c->resp.status); |
561 | 0 | } |
562 | 0 | out_ret = FLB_RETRY; |
563 | 0 | } |
564 | 0 | else { |
565 | 0 | if (c->resp.payload) { |
566 | 0 | flb_plg_info(ctx->ins, "%s:%i, HTTP status=%i\n%s", |
567 | 0 | ctx->logdna_host, ctx->logdna_port, |
568 | 0 | c->resp.status, c->resp.payload); |
569 | 0 | } |
570 | 0 | else { |
571 | 0 | flb_plg_info(ctx->ins, "%s:%i, HTTP status=%i", |
572 | 0 | ctx->logdna_host, ctx->logdna_port, |
573 | 0 | c->resp.status); |
574 | 0 | } |
575 | 0 | } |
576 | 0 | } |
577 | 0 | else { |
578 | 0 | flb_plg_error(ctx->ins, "could not flush records to %s:%s (http_do=%i)", |
579 | 0 | FLB_LOGDNA_HOST, FLB_LOGDNA_PORT, ret); |
580 | 0 | out_ret = FLB_RETRY; |
581 | 0 | } |
582 | |
|
583 | 0 | flb_http_client_destroy(c); |
584 | 0 | flb_upstream_conn_release(u_conn); |
585 | 0 | FLB_OUTPUT_RETURN(out_ret); |
586 | 0 | } |
587 | | |
588 | | static int cb_logdna_exit(void *data, struct flb_config *config) |
589 | 0 | { |
590 | 0 | struct flb_logdna *ctx = data; |
591 | |
|
592 | 0 | if (!ctx) { |
593 | 0 | return 0; |
594 | 0 | } |
595 | | |
596 | 0 | if (ctx->_hostname) { |
597 | 0 | flb_sds_destroy(ctx->_hostname); |
598 | 0 | } |
599 | 0 | logdna_config_destroy(ctx); |
600 | 0 | return 0; |
601 | 0 | } |
602 | | |
603 | | /* Configuration properties map */ |
604 | | static struct flb_config_map config_map[] = { |
605 | | { |
606 | | FLB_CONFIG_MAP_STR, "logdna_host", FLB_LOGDNA_HOST, |
607 | | 0, FLB_TRUE, offsetof(struct flb_logdna, logdna_host), |
608 | | "LogDNA Host address" |
609 | | }, |
610 | | |
611 | | { |
612 | | FLB_CONFIG_MAP_INT, "logdna_port", FLB_LOGDNA_PORT, |
613 | | 0, FLB_TRUE, offsetof(struct flb_logdna, logdna_port), |
614 | | "LogDNA TCP port" |
615 | | }, |
616 | | |
617 | | { |
618 | | FLB_CONFIG_MAP_STR, "logdna_endpoint", FLB_LOGDNA_ENDPOINT, |
619 | | 0, FLB_TRUE, offsetof(struct flb_logdna, logdna_endpoint), |
620 | | "LogDNA endpoint to send logs" |
621 | | }, |
622 | | |
623 | | { |
624 | | FLB_CONFIG_MAP_STR, "api_key", NULL, |
625 | | 0, FLB_TRUE, offsetof(struct flb_logdna, api_key), |
626 | | "Logdna API key" |
627 | | }, |
628 | | |
629 | | { |
630 | | FLB_CONFIG_MAP_STR, "hostname", NULL, |
631 | | 0, FLB_TRUE, offsetof(struct flb_logdna, hostname), |
632 | | "Local Server or device host name" |
633 | | }, |
634 | | |
635 | | { |
636 | | FLB_CONFIG_MAP_STR, "mac", "", |
637 | | 0, FLB_TRUE, offsetof(struct flb_logdna, mac_addr), |
638 | | "MAC address (optional)" |
639 | | }, |
640 | | |
641 | | { |
642 | | FLB_CONFIG_MAP_STR, "ip", "", |
643 | | 0, FLB_TRUE, offsetof(struct flb_logdna, ip_addr), |
644 | | "IP address (optional)" |
645 | | }, |
646 | | |
647 | | { |
648 | | FLB_CONFIG_MAP_CLIST, "tags", "", |
649 | | 0, FLB_TRUE, offsetof(struct flb_logdna, tags), |
650 | | "Tags (optional)" |
651 | | }, |
652 | | |
653 | | { |
654 | | FLB_CONFIG_MAP_STR, "file", NULL, |
655 | | 0, FLB_TRUE, offsetof(struct flb_logdna, file), |
656 | | "Name of the monitored file (optional)" |
657 | | }, |
658 | | |
659 | | { |
660 | | FLB_CONFIG_MAP_STR, "app", "Fluent Bit", |
661 | | 0, FLB_TRUE, offsetof(struct flb_logdna, app), |
662 | | "Name of the application generating the data (optional)" |
663 | | }, |
664 | | |
665 | | { |
666 | | FLB_CONFIG_MAP_BOOL, "exclude_promoted_keys", "false", |
667 | | 0, FLB_TRUE, offsetof(struct flb_logdna, exclude_promoted_keys), |
668 | | "Exclude promoted keys (meta, level, severity (promoted as level), app, file) from the line body" |
669 | | }, |
670 | | |
671 | | /* EOF */ |
672 | | {0} |
673 | | |
674 | | }; |
675 | | |
676 | | static int cb_logdna_format_test(struct flb_config *config, |
677 | | struct flb_input_instance *ins, |
678 | | void *plugin_context, |
679 | | void *flush_ctx, |
680 | | int event_type, |
681 | | const char *tag, int tag_len, |
682 | | const void *data, size_t bytes, |
683 | | void **out_data, size_t *out_size) |
684 | 0 | { |
685 | 0 | flb_sds_t json; |
686 | 0 | struct flb_logdna *ctx = plugin_context; |
687 | |
|
688 | 0 | json = logdna_compose_payload(ctx, data, bytes, tag, tag_len, config); |
689 | 0 | if (!json) { |
690 | 0 | return -1; |
691 | 0 | } |
692 | | |
693 | 0 | *out_data = json; |
694 | 0 | *out_size = flb_sds_len(json); |
695 | 0 | return 0; |
696 | 0 | } |
697 | | |
698 | | /* Plugin reference */ |
699 | | struct flb_output_plugin out_logdna_plugin = { |
700 | | .name = "logdna", |
701 | | .description = "LogDNA", |
702 | | .cb_init = cb_logdna_init, |
703 | | .cb_flush = cb_logdna_flush, |
704 | | .cb_exit = cb_logdna_exit, |
705 | | .config_map = config_map, |
706 | | .test_formatter.callback = cb_logdna_format_test, |
707 | | .flags = FLB_OUTPUT_NET | FLB_IO_TLS, |
708 | | }; |