Skip to content

Commit

Permalink
Merge pull request #402 from sodomelle/filterx_strftime_func
Browse files Browse the repository at this point in the history
filterx: strftime
  • Loading branch information
alltilla authored Dec 2, 2024
2 parents 2246b77 + 0c647e3 commit 8aed412
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 2 deletions.
3 changes: 3 additions & 0 deletions lib/filterx/filterx-globals.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/*
* Copyright (c) 2024 Axoflow
* Copyright (c) 2024 Tamás Kosztyu <tamas.kosztyu@axoflow.com>
* Copyright (c) 2023 Balazs Scheidler <balazs.scheidler@axoflow.com>
*
* This library is free software; you can redistribute it and/or
Expand Down Expand Up @@ -145,6 +147,7 @@ _ctors_init(void)
g_assert(filterx_builtin_function_ctor_register("startswith", filterx_function_startswith_new));
g_assert(filterx_builtin_function_ctor_register("endswith", filterx_function_endswith_new));
g_assert(filterx_builtin_function_ctor_register("includes", filterx_function_includes_new));
g_assert(filterx_builtin_function_ctor_register("strftime", filterx_function_strftime_new));
}

static void
Expand Down
144 changes: 142 additions & 2 deletions lib/filterx/object-datetime.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/*
* Copyright (c) 2024 Axoflow
* Copyright (c) 2024 Tamás Kosztyu <tamas.kosztyu@axoflow.com>
* Copyright (c) 2023 Balazs Scheidler <balazs.scheidler@axoflow.com>
*
* This library is free software; you can redistribute it and/or
Expand Down Expand Up @@ -41,6 +43,7 @@
#include "compat/json.h"

#define FILTERX_FUNC_STRPTIME_USAGE "Usage: strptime(time_str, format_str_1, ..., format_str_N)"
#define FILTERX_FUNC_STRFTIME_USAGE "Usage: strftime(fornat_str, datetime)"

typedef struct _FilterXDateTime
{
Expand Down Expand Up @@ -348,7 +351,7 @@ _extract_time_str_expr(FilterXFunctionArgs *args, GError **error)
}

static gboolean
_extract_args(FilterXFunctionStrptime *self, FilterXFunctionArgs *args, GError **error)
_extract_strptime_args(FilterXFunctionStrptime *self, FilterXFunctionArgs *args, GError **error)
{
gsize len = filterx_function_args_len(args);
if (len < 2)
Expand Down Expand Up @@ -378,7 +381,7 @@ filterx_function_strptime_new(FilterXFunctionArgs *args, GError **error)
self->super.super.eval = _strptime_eval;
self->super.super.free_fn = _strptime_free;

if (!_extract_args(self, args, error) ||
if (!_extract_strptime_args(self, args, error) ||
!filterx_function_args_check(args, error))
goto error;

Expand All @@ -391,6 +394,143 @@ filterx_function_strptime_new(FilterXFunctionArgs *args, GError **error)
return NULL;
}

typedef struct FilterXFunctionStrftime_
{
FilterXFunction super;
const gchar *format;
FilterXExpr *datetime_expr;
} FilterXFunctionStrftime;

static FilterXObject *
_strftime_eval(FilterXExpr *s)
{
FilterXFunctionStrftime *self = (FilterXFunctionStrftime *) s;

FilterXObject *datetime_obj = filterx_expr_eval(self->datetime_expr);
if (!datetime_obj)
{
filterx_eval_push_error("Failed to evaluate second argument. " FILTERX_FUNC_STRFTIME_USAGE, s, NULL);
return NULL;
}

UnixTime datetime = UNIX_TIME_INIT;
WallClockTime wct = WALL_CLOCK_TIME_INIT;

if (!filterx_object_extract_datetime(datetime_obj, &datetime))
{
filterx_object_unref(datetime_obj);
filterx_eval_push_error("Second argument must be of datetime type. " FILTERX_FUNC_STRFTIME_USAGE, s, NULL);
return NULL;
}

filterx_object_unref(datetime_obj);

convert_unix_time_to_wall_clock_time (&datetime, &wct);

const gsize MAX_RESULT_STR_LEN = 256;
size_t date_size = 0;
gchar result_str[MAX_RESULT_STR_LEN];

// TODO - implement wall_clock_time_strftime, because there is some inconsistency with the format accepted by wall_clock_time_strptime
date_size = strftime(result_str, MAX_RESULT_STR_LEN, self->format, &wct.tm);

if (!date_size)
{
return filterx_null_new();
}

return filterx_string_new(result_str, strnlen(result_str, MAX_RESULT_STR_LEN));
}

static void
_strftime_free(FilterXExpr *s)
{
FilterXFunctionStrftime *self = (FilterXFunctionStrftime *) s;

filterx_expr_unref(self->datetime_expr);
filterx_function_free_method(&self->super);
}

static const gchar *
_extract_strftime_format(FilterXFunctionArgs *args, GError **error)
{
const gchar *format = filterx_function_args_get_literal_string(args, 0, NULL);
if (!format)
{
g_set_error(error, FILTERX_FUNCTION_ERROR, FILTERX_FUNCTION_ERROR_CTOR_FAIL,
"argument must be set: format_str. " FILTERX_FUNC_STRFTIME_USAGE);
return NULL;
}

return format;
}

static FilterXExpr *
_extract_strftime_datetime_expr(FilterXFunctionArgs *args, GError **error)
{
FilterXExpr *datetime_expr = filterx_function_args_get_expr(args, 1);
if (!datetime_expr)
{
g_set_error(error, FILTERX_FUNCTION_ERROR, FILTERX_FUNCTION_ERROR_CTOR_FAIL,
"argument must be set: time_str. " FILTERX_FUNC_STRFTIME_USAGE);
return NULL;
}

return datetime_expr;
}

static gboolean
_extract_strftime_args(FilterXFunctionStrftime *self, FilterXFunctionArgs *args, GError **error)
{
gsize len = filterx_function_args_len(args);

if (len != 2)
{
g_set_error(error, FILTERX_FUNCTION_ERROR, FILTERX_FUNCTION_ERROR_CTOR_FAIL,
"invalid number of arguments. " FILTERX_FUNC_STRFTIME_USAGE);
return FALSE;
}

self->format = _extract_strftime_format(args, error);
if (!self->format)
{
return FALSE;
}

self->datetime_expr = _extract_strftime_datetime_expr(args, error);
if (!self->format)
{
return FALSE;
}

return TRUE;
}

/* Takes reference of args */
FilterXExpr *
filterx_function_strftime_new(FilterXFunctionArgs *args, GError **error)
{
FilterXFunctionStrftime *self = g_new0(FilterXFunctionStrftime, 1);
filterx_function_init_instance(&self->super, "strftime");

self->super.super.eval = _strftime_eval;
self->super.super.free_fn = _strftime_free;

if (!_extract_strftime_args(self, args, error) ||
!filterx_function_args_check(args, error))
{
goto error;
}

filterx_function_args_free(args);
return &self->super.super;

error:
filterx_function_args_free(args);
filterx_expr_unref(&self->super.super);
return NULL;
}

FILTERX_DEFINE_TYPE(datetime, FILTERX_TYPE_NAME(object),
.truthy = _truthy,
.map_to_json = _map_to_json,
Expand Down
3 changes: 3 additions & 0 deletions lib/filterx/object-datetime.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/*
* Copyright (c) 2024 Axoflow
* Copyright (c) 2024 Tamás Kosztyu <tamas.kosztyu@axoflow.com>
* Copyright (c) 2023 Balazs Scheidler <balazs.scheidler@axoflow.com>
*
* This library is free software; you can redistribute it and/or
Expand Down Expand Up @@ -35,6 +37,7 @@ UnixTime filterx_datetime_get_value(FilterXObject *s);
FilterXObject *filterx_typecast_datetime(FilterXExpr *s, GPtrArray *args);
FilterXObject *filterx_typecast_datetime_isodate(FilterXExpr *, GPtrArray *args);
FilterXExpr *filterx_function_strptime_new(FilterXFunctionArgs *args, GError **error);
FilterXExpr *filterx_function_strftime_new(FilterXFunctionArgs *args, GError **error);

gboolean datetime_repr(const UnixTime *ut, GString *repr);

Expand Down
9 changes: 9 additions & 0 deletions news/fx-feature-402.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
`strftime()`: Added new filterx function to format datetimes.

Example usage:
```
$MSG = strftime("%Y-%m-%dT%H:%M:%S %z", datetime);
```

Note: `%Z` currently does not respect the datetime's timezone,
usage of `%z` works as expected, and advised.
26 changes: 26 additions & 0 deletions tests/light/functional_tests/filterx/test_filterx.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env python
#############################################################################
# Copyright (c) 2024 Axoflow
# Copyright (c) 2024 Tamás Kosztyu <tamas.kosztyu@axoflow.com>
# Copyright (c) 2023 Balazs Scheidler <balazs.scheidler@axoflow.com>
#
# This program is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -1179,6 +1181,16 @@ def test_strptime_error_result(config, syslog_ng):
syslog_ng.start(config)


def test_strftime_error_result(config, syslog_ng):
_ = create_config(
config, """
$MSG = strftime("2024-04-10T08:09:10Z"); # wrong arg set
""",
)
with pytest.raises(Exception):
syslog_ng.start(config)


def test_strptime_success_result(config, syslog_ng):
(file_true, file_false) = create_config(
config, """
Expand All @@ -1192,6 +1204,20 @@ def test_strptime_success_result(config, syslog_ng):
assert file_true.read_log() == "1712736550.000000+00:00\n"


def test_strftime_success_result(config, syslog_ng):
(file_true, file_false) = create_config(
config, """
datetime = strptime("2000-01-01T00:00:00 +0200", "%Y-%m-%dT%H:%M:%S %z");
$MSG = strftime("%Y-%m-%dT%H:%M:%S %z", datetime);
""",
)
syslog_ng.start(config)

assert file_true.get_stats()["processed"] == 1
assert "processed" not in file_false.get_stats()
assert file_true.read_log() == "2000-01-01T00:00:00 +0200\n"


def test_strptime_guess_missing_year(config, syslog_ng):
(file_true, file_false) = create_config(
config, r"""
Expand Down

0 comments on commit 8aed412

Please sign in to comment.