示例#1
0
static tlog_grc
tlog_journal_json_reader_init(struct tlog_json_reader *reader, va_list ap)
{
    struct tlog_journal_json_reader *journal_json_reader =
                                (struct tlog_journal_json_reader*)reader;
    uint64_t since = va_arg(ap, uint64_t);
    uint64_t until = va_arg(ap, uint64_t);
    const char * const *match_sym_list = va_arg(ap, const char * const *);
    int sd_rc;
    tlog_grc grc;

    /* Create JSON tokener */
    journal_json_reader->tok = json_tokener_new();
    if (journal_json_reader->tok == NULL) {
        grc = TLOG_GRC_ERRNO;
        goto error;
    }

    /* Open journal */
    sd_rc = sd_journal_open(&journal_json_reader->journal, 0);
    if (sd_rc < 0) {
        grc = TLOG_GRC_FROM(systemd, sd_rc);
        goto error;
    }

    /* Add matches */
    sd_rc = tlog_journal_add_match_sym_list(journal_json_reader->journal,
                                            match_sym_list);
    if (sd_rc < 0) {
        grc = TLOG_GRC_FROM(systemd, sd_rc);
        goto error;
    }

    /* Seek to "since" timestamp */
    sd_rc = sd_journal_seek_realtime_usec(journal_json_reader->journal,
                                          since);
    if (sd_rc < 0) {
        grc = TLOG_GRC_FROM(systemd, sd_rc);
        goto error;
    }

    /* Store "until" timestamp */
    journal_json_reader->until = until;

    return TLOG_RC_OK;

error:
    tlog_journal_json_reader_cleanup(reader);
    return grc;
}
示例#2
0
文件: tlog-rec.c 项目: opuk/tlog
/**
 * Get fully-qualified name of this host.
 *
 * @param pfqdn     Location for the dynamically-allocated name.
 *
 * @return Status code.
 */
static tlog_grc
get_fqdn(char **pfqdn)
{
    char hostname[HOST_NAME_MAX + 1];
    struct addrinfo hints = {.ai_family = AF_UNSPEC,
                             .ai_flags  = AI_CANONNAME};
    struct addrinfo *info;
    int gai_error;

    assert(pfqdn != NULL);

    /* Get hostname */
    if (gethostname(hostname, sizeof(hostname)) < 0)
        return TLOG_GRC_ERRNO;

    /* Resolve hostname to FQDN */
    gai_error = getaddrinfo(hostname, NULL, &hints, &info);
    if (gai_error != 0)
        return TLOG_GRC_FROM(gai, gai_error);

    /* Duplicate retrieved FQDN */
    *pfqdn = strdup(info->ai_canonname);
    freeaddrinfo(info);
    if (*pfqdn == NULL)
        return TLOG_GRC_ERRNO;

    return TLOG_RC_OK;
}
示例#3
0
static tlog_grc
tlog_journal_json_writer_write(struct tlog_json_writer *writer,
                               size_t id, const uint8_t *buf, size_t len)
{
    struct tlog_journal_json_writer *journal_json_writer =
                                    (struct tlog_journal_json_writer*)writer;
    int sd_rc;

#define BASE_ARGS \
    "PRIORITY=%d", journal_json_writer->priority,   \
    "MESSAGE=%.*s", len, buf

    if (journal_json_writer->augment) {
        sd_rc = sd_journal_send(
                    "TLOG_REC=%s", journal_json_writer->recording,
                    "TLOG_USER=%s", journal_json_writer->username,
                    "TLOG_SESSION=%u", journal_json_writer->session_id,
                    "TLOG_ID=%zu", id,
                    BASE_ARGS,
                    NULL);
    } else {
        sd_rc = sd_journal_send(BASE_ARGS, NULL);
    }

#undef BASE_ARGS

    return (sd_rc < 0) ? TLOG_GRC_FROM(systemd, sd_rc) : TLOG_RC_OK;
}
示例#4
0
文件: es_reader.c 项目: opuk/tlog
/**
 * Format an ElasticSearch request URL prefix: the URL ending with "from=",
 * ready for the addition of the message index.
 *
 * @param purl_pfx  The location for the dynamically-allocated URL prefix.
 * @param curl      The CURL handle to escape with.
 * @param base_url  The base URL to request ElasticSearch, without the query
 *                  or the fragment parts.
 * @param query     The query string to send to ElastiSearch.
 * @param size      Number of messages to request from ElasticSearch in one
 *                  HTTP request.
 *
 * @return Global return code.
 */
static tlog_grc
tlog_es_reader_format_url_pfx(char **purl_pfx,
                              CURL *curl,
                              const char *base_url,
                              const char *query,
                              size_t size)
{
    static const char *sort = "id:asc";

    tlog_grc grc;
    char *url_pfx;
    char *esc_query = NULL;
    char *esc_sort = NULL;

    assert(purl_pfx != NULL);
    assert(curl != NULL);
    assert(tlog_es_reader_base_url_is_valid(base_url));
    assert(query != NULL);
    assert(size >= TLOG_ES_READER_SIZE_MIN);

#define ESC(_token) \
    do {                                                    \
        esc_##_token = curl_easy_escape(curl, _token, 0);   \
        if (esc_##_token == NULL) {                         \
            grc = TLOG_GRC_FROM(errno, ENOMEM);             \
            goto cleanup;                                   \
        }                                                   \
    } while (0)

    ESC(query);
    ESC(sort);

#undef ESC

    if (asprintf(&url_pfx,
                 "%s?q=%s&fields=&_source&sort=%s&size=%zu&from=",
                 base_url, esc_query, esc_sort, size) < 0) {
        grc = TLOG_GRC_FROM(errno, ENOMEM);
        goto cleanup;
    }

    *purl_pfx = url_pfx;

    grc = TLOG_RC_OK;

cleanup:

    if (esc_sort != NULL) {
        curl_free(esc_sort);
    }
    if (esc_query != NULL) {
        curl_free(esc_query);
    }
    return grc;
}
示例#5
0
文件: es_reader.c 项目: opuk/tlog
static tlog_grc
tlog_es_reader_init(struct tlog_reader *reader, va_list ap)
{
    struct tlog_es_reader *es_reader =
                                (struct tlog_es_reader*)reader;
    const char *base_url = va_arg(ap, const char *);
    const char *query = va_arg(ap, const char *);
    size_t size = va_arg(ap, size_t);
    CURLcode rc;
    tlog_grc grc;

    assert(tlog_es_reader_base_url_is_valid(base_url));
    assert(query != NULL);
    assert(size >= TLOG_ES_READER_SIZE_MIN);

    /* Create and initialize CURL handle */
    es_reader->curl = curl_easy_init();
    if (es_reader->curl == NULL) {
        grc = TLOG_RC_ES_READER_CURL_INIT_FAILED;
        goto error;
    }
    rc = curl_easy_setopt(es_reader->curl, CURLOPT_WRITEFUNCTION,
                          tlog_es_reader_write_func);
    if (rc != CURLE_OK) {
        grc = TLOG_GRC_FROM(curl, rc);
        goto error;
    }

    /* Format URL prefix */
    grc = tlog_es_reader_format_url_pfx(&es_reader->url_pfx,
                                        es_reader->curl,
                                        base_url, query, size);
    if (grc != TLOG_RC_OK) {
        goto error;
    }

    /* Set request size */
    es_reader->size = size;

    /* Create JSON tokener */
    es_reader->tok = json_tokener_new();
    if (es_reader->tok == NULL) {
        grc = TLOG_GRC_ERRNO;
        goto error;
    }

    return TLOG_RC_OK;

error:
    tlog_es_reader_cleanup(reader);
    return grc;
}
示例#6
0
文件: tlog-rec.c 项目: opuk/tlog
/**
 * Get process session ID.
 *
 * @param pid   Location for the retrieved session ID.
 *
 * @return Global return code.
 */
static tlog_grc
get_session_id(unsigned int *pid)
{
    FILE *file;
    int orig_errno;
    int rc;

    assert(pid != NULL);

    file = fopen("/proc/self/sessionid", "r");
    if (file == NULL)
        return TLOG_RC_FAILURE;
    rc = fscanf(file, "%u", pid);
    orig_errno = errno;
    fclose(file);
    if (rc == 1)
        return TLOG_RC_OK;
    if (rc == 0)
        return TLOG_GRC_FROM(errno, EINVAL);

    return TLOG_GRC_FROM(errno, orig_errno);
}
示例#7
0
文件: es_reader.c 项目: opuk/tlog
/**
 * Format an ElasticSearch request URL for the current message index of a
 * reader.
 *
 * @param reader    The reader to format the URL for.
 * @param purl      The location for the dynamically-allocated URL.
 *
 * @return Global return code.
 */
static tlog_grc
tlog_es_reader_format_url(struct tlog_es_reader *es_reader, char **purl)
{
    char *url;

    assert(tlog_es_reader_is_valid((struct tlog_reader*)es_reader));

    if (asprintf(&url, "%s%zu", es_reader->url_pfx, es_reader->idx) < 0) {
        return TLOG_GRC_FROM(errno, ENOMEM);
    }

    *purl = url;

    return TLOG_RC_OK;
}
示例#8
0
/**
 * Read the memory reader text as a JSON object line, don't consume
 * terminating newline. The line must not start with whitespace.
 *
 * @param mem_json_reader    The memory reader to parse the object for.
 * @param pobject       Location for the parsed object pointer.
 *
 * @return Global return code.
 */
static tlog_grc
tlog_mem_json_reader_read_json(struct tlog_mem_json_reader *mem_json_reader,
                               struct json_object **pobject)
{
    const char *p;
    struct json_object *object;
    enum json_tokener_error jerr;

    assert(tlog_mem_json_reader_is_valid(
                    (struct tlog_json_reader *)mem_json_reader));
    assert(pobject != NULL);

    json_tokener_reset(mem_json_reader->tok);

    /* Look for a terminating newline */
    for (p = mem_json_reader->pos;
         p < mem_json_reader->end && *p != '\n';
         p++);

    /* If the line is empty */
    if (p == mem_json_reader->pos) {
        /* Report EOF */
        *pobject = NULL;
        return TLOG_RC_OK;
    }

    /* Parse the line */
    object = json_tokener_parse_ex(mem_json_reader->tok,
                                   mem_json_reader->pos,
                                   p - mem_json_reader->pos);
    mem_json_reader->pos = p;
    if (object == NULL) {
        jerr = json_tokener_get_error(mem_json_reader->tok);
        return (jerr == json_tokener_continue)
                    ? TLOG_RC_MEM_JSON_READER_INCOMPLETE_LINE
                    : TLOG_GRC_FROM(json, jerr);
    }

    /* Return the parsed object */
    *pobject = object;
    return TLOG_RC_OK;
}
示例#9
0
tlog_grc
tlog_journal_json_reader_read(struct tlog_json_reader *reader,
                              struct json_object **pobject)
{
    struct tlog_journal_json_reader *journal_json_reader =
                                (struct tlog_journal_json_reader*)reader;
    tlog_grc grc;
    int sd_rc;
    struct json_object *object = NULL;

    /* If we ran out of time limit */
    if (journal_json_reader->last > journal_json_reader->until) {
        goto exit;
    }

    /* Advance to the next entry */
    sd_rc = sd_journal_next(journal_json_reader->journal);
    /* If failed */
    if (sd_rc < 0) {
        grc = TLOG_GRC_FROM(systemd, sd_rc);
        goto cleanup;
    /* If got an entry */
    } else if (sd_rc > 0) {
        const char *field_ptr;
        size_t field_len;
        const char *message_ptr;
        size_t message_len;

        /* Advance entry counter */
        journal_json_reader->entry++;

        /* Get the entry realtime timestamp */
        sd_rc = sd_journal_get_realtime_usec(journal_json_reader->journal,
                                             &journal_json_reader->last);
        if (sd_rc < 0) {
            grc = TLOG_GRC_FROM(systemd, sd_rc);
            goto cleanup;
        }
        if (journal_json_reader->last > journal_json_reader->until) {
            goto exit;
        }

        /* Get the entry message field data */
        sd_rc = sd_journal_get_data(journal_json_reader->journal, "MESSAGE",
                                    (const void **)&field_ptr, &field_len);
        if (sd_rc < 0) {
            grc = TLOG_GRC_FROM(systemd, sd_rc);
            goto cleanup;
        }

        /* Extract the message */
        message_ptr = (const char *)memchr(field_ptr, '=', field_len);
        if (message_ptr == NULL) {
            grc = TLOG_RC_FAILURE;
            goto cleanup;
        }
        message_ptr++;
        message_len = field_len - (message_ptr - field_ptr);

        /* Parse the message */
        object = json_tokener_parse_ex(journal_json_reader->tok,
                                       message_ptr, message_len);
        if (object == NULL) {
            grc = TLOG_GRC_FROM(
                    json, json_tokener_get_error(journal_json_reader->tok));
            goto cleanup;
        }
    }

exit:
    *pobject = object;
    object = NULL;
    grc = TLOG_RC_OK;
cleanup:
    json_object_put(object);
    return grc;
}
示例#10
0
文件: es_reader.c 项目: opuk/tlog
/**
 * Refill the retrieved message array of a reader.
 *
 * @param reader    The reader to refill the message array for.
 *
 * @return Global return code.
 */
static tlog_grc
tlog_es_reader_refill_array(struct tlog_es_reader *es_reader)
{
    tlog_grc grc;
    char *url = NULL;
    struct tlog_es_reader_write_data data;
    CURLcode rc;

    assert(tlog_es_reader_is_valid((struct tlog_reader *)es_reader));

    /* Free the previous array, if any */
    if (es_reader->array != NULL) {
        json_object_put(es_reader->array);
        es_reader->array = NULL;
        es_reader->array_len = 0;
    }

    /* Format request URL */
    grc = tlog_es_reader_format_url(es_reader, &url);
    if (grc != TLOG_RC_OK) {
        goto cleanup;
    }

    /* Set request URL */
    rc = curl_easy_setopt(es_reader->curl, CURLOPT_URL, url);
    if (rc != CURLE_OK) {
        grc = TLOG_GRC_FROM(curl, rc);
        goto cleanup;
    }

    /* Set callback data */
    data.tok = es_reader->tok;
    data.rc = json_tokener_success;
    data.obj = NULL;
    rc = curl_easy_setopt(es_reader->curl, CURLOPT_WRITEDATA, &data);
    if (rc != CURLE_OK) {
        grc = TLOG_GRC_FROM(curl, rc);
        goto cleanup;
    }

    /* Perform the request */
    rc = curl_easy_perform(es_reader->curl);
    if (rc != CURLE_OK) {
        if (rc == CURLE_WRITE_ERROR && data.rc != json_tokener_success) {
            grc = TLOG_GRC_FROM(json, data.rc);
        } else {
            grc = TLOG_GRC_FROM(curl, rc);
        }
        goto cleanup;
    }

    /* If no data was read */
    if (data.obj == NULL) {
        es_reader->array = NULL;
        es_reader->array_len = 0;
    } else {
        struct json_object *obj;
        /* Extract the array */
        if (!json_object_object_get_ex(data.obj, "hits", &obj) ||
            !json_object_object_get_ex(obj, "hits", &obj) ||
            json_object_get_type(obj) != json_type_array) {
            grc = TLOG_RC_ES_READER_REPLY_INVALID;
            goto cleanup;
        }
        es_reader->array = json_object_get(obj);
        es_reader->array_len = json_object_array_length(obj);
    }

    es_reader->array_idx = es_reader->idx;

    grc = TLOG_RC_OK;

cleanup:

    if (data.obj != NULL) {
        json_object_put(data.obj);
    }
    free(url);
    return grc;
}
示例#11
0
/**
 * Read the fd reader text as (a part of) a JSON object line, don't consume
 * terminating newline.
 *
 * @param fd_json_reader     The fd reader to parse the object for.
 * @param pobject       Location for the parsed object pointer.
 *
 * @return Global return code.
 */
static tlog_grc
tlog_fd_json_reader_read_json(struct tlog_fd_json_reader *fd_json_reader,
                              struct json_object **pobject)
{
    tlog_grc grc;
    char *p;
    struct json_object *object;
    enum json_tokener_error jerr;
    bool got_text = false;

    assert(tlog_fd_json_reader_is_valid(
                    (struct tlog_json_reader *)fd_json_reader));
    assert(pobject != NULL);

    json_tokener_reset(fd_json_reader->tok);

    /* Until EOF */
    do {
        /* If the buffer is not empty */
        if (fd_json_reader->pos < fd_json_reader->end) {
            /* We got something to parse */
            got_text = true;

            /* Look for a terminating newline */
            for (p = fd_json_reader->pos;
                 p < fd_json_reader->end && *p != '\n';
                 p++);

            /* Parse the next piece */
            object = json_tokener_parse_ex(fd_json_reader->tok,
                                           fd_json_reader->pos,
                                           p - fd_json_reader->pos);
            fd_json_reader->pos = p;

            /* If we finished parsing an object */
            if (object != NULL) {
                *pobject = object;
                return TLOG_RC_OK;
            } else {
                jerr = json_tokener_get_error(fd_json_reader->tok);
                /* If object is not finished */
                if (jerr == json_tokener_continue) {
                    /* If we encountered an object-terminating newline */
                    if (p < fd_json_reader->end) {
                        return TLOG_RC_FD_JSON_READER_INCOMPLETE_LINE;
                    }
                } else {
                    return TLOG_GRC_FROM(json, jerr);
                }
            }
        }

        grc = tlog_fd_json_reader_refill_buf(fd_json_reader);
        if (grc != TLOG_RC_OK)
            return grc;
    } while (fd_json_reader->end > fd_json_reader->buf);

    if (got_text) {
        return TLOG_RC_FD_JSON_READER_INCOMPLETE_LINE;
    } else {
        *pobject = NULL;
        return TLOG_RC_OK;
    }
}
示例#12
0
文件: tlog-rec.c 项目: jhrozek/tlog
/**
 * Transfer and log terminal data until interrupted or either end closes.
 *
 * @param tty_source    TTY data source.
 * @param log_sink      Log sink.
 * @param tty_sink      TTY data sink.
 * @param latency       Number of seconds to wait before flushing logged data.
 * @param log_mask      Logging mask with bits indexed by enum tlog_log_item.
 *
 * @return Global return code.
 */
static tlog_grc
transfer(struct tlog_source    *tty_source,
         struct tlog_sink      *log_sink,
         struct tlog_sink      *tty_sink,
         unsigned int           latency,
         unsigned int           log_mask)
{
    const int exit_sig[] = {SIGINT, SIGTERM, SIGHUP};
    tlog_grc return_grc = TLOG_RC_OK;
    tlog_grc grc;
    size_t i, j;
    struct sigaction sa;
    bool alarm_set = false;
    bool log_pending = false;
    sig_atomic_t last_alarm_caught = 0;
    sig_atomic_t new_alarm_caught;
    struct tlog_pkt pkt = TLOG_PKT_VOID;
    struct tlog_pkt_pos tty_pos = TLOG_PKT_POS_VOID;
    struct tlog_pkt_pos log_pos = TLOG_PKT_POS_VOID;

    /* Setup signal handlers to terminate gracefully */
    for (i = 0; i < TLOG_ARRAY_SIZE(exit_sig); i++) {
        sigaction(exit_sig[i], NULL, &sa);
        if (sa.sa_handler != SIG_IGN)
        {
            sa.sa_handler = exit_sighandler;
            sigemptyset(&sa.sa_mask);
            for (j = 0; j < TLOG_ARRAY_SIZE(exit_sig); j++)
                sigaddset(&sa.sa_mask, exit_sig[j]);
            /* NOTE: no SA_RESTART on purpose */
            sa.sa_flags = 0;
            sigaction(exit_sig[i], &sa, NULL);
        }
    }

    /* Setup ALRM signal handler */
    sa.sa_handler = alarm_sighandler;
    sigemptyset(&sa.sa_mask);
    /* NOTE: no SA_RESTART on purpose */
    sa.sa_flags = 0;
    sigaction(SIGALRM, &sa, NULL);

    /*
     * Transfer I/O and window changes
     */
    while (exit_signum == 0) {
        /* Handle latency limit */
        new_alarm_caught = alarm_caught;
        if (new_alarm_caught != last_alarm_caught) {
            alarm_set = false;
            grc = tlog_sink_flush(log_sink);
            if (grc != TLOG_RC_OK) {
                fprintf(stderr, "Failed flushing log: %s\n",
                        tlog_grc_strerror(grc));
                return_grc = grc;
                goto cleanup;
            }
            last_alarm_caught = new_alarm_caught;
            log_pending = false;
        } else if (log_pending && !alarm_set) {
            alarm(latency);
            alarm_set = true;
        }

        /* Deliver logged data if any */
        if (tlog_pkt_pos_cmp(&tty_pos, &log_pos) < 0) {
            grc = tlog_sink_write(tty_sink, &pkt, &tty_pos, &log_pos);
            if (grc != TLOG_RC_OK) {
                if (grc == TLOG_GRC_FROM(errno, EINTR)) {
                    continue;
                } else if (grc != TLOG_GRC_FROM(errno, EBADF) &&
                           grc != TLOG_GRC_FROM(errno, EINVAL)) {
                    fprintf(stderr, "Failed writing terminal data: %s\n",
                            tlog_grc_strerror(grc));
                    return_grc = grc;
                }
                break;
            }
            continue;
        }

        /* Log the received data, if any */
        if (tlog_pkt_pos_is_in(&log_pos, &pkt)) {
            /* If asked to log this type of packet */
            if (log_mask & (1 << tlog_log_item_from_pkt(&pkt))) {
                grc = tlog_sink_write(log_sink, &pkt, &log_pos, NULL);
                if (grc != TLOG_RC_OK &&
                    grc != TLOG_GRC_FROM(errno, EINTR)) {
                    fprintf(stderr, "Failed logging terminal data: %s\n",
                            tlog_grc_strerror(grc));
                    return_grc = grc;
                    goto cleanup;
                }
                log_pending = true;
            } else {
                tlog_pkt_pos_move_past(&log_pos, &pkt);
            }
            continue;
        }

        /* Read new data */
        tlog_pkt_cleanup(&pkt);
        log_pos = TLOG_PKT_POS_VOID;
        tty_pos = TLOG_PKT_POS_VOID;
        grc = tlog_source_read(tty_source, &pkt);
        if (grc != TLOG_RC_OK) {
            if (grc == TLOG_GRC_FROM(errno, EINTR)) {
                continue;
            } else if (grc != TLOG_GRC_FROM(errno, EBADF) &&
                       grc != TLOG_GRC_FROM(errno, EIO)) {
                fprintf(stderr, "Failed reading terminal data: %s\n",
                        tlog_grc_strerror(grc));
                return_grc = grc;
            }
            break;
        } else if (tlog_pkt_is_void(&pkt)) {
            break;
        }
    }

    /* Cut the log (write incomplete characters as binary) */
    grc = tlog_sink_cut(log_sink);
    if (grc != TLOG_RC_OK) {
        fprintf(stderr, "Failed cutting-off the log: %s\n",
                tlog_grc_strerror(grc));
        if (return_grc == TLOG_RC_OK) {
            return_grc = grc;
        }
        goto cleanup;
    }

    /* Flush the log */
    grc = tlog_sink_flush(log_sink);
    if (grc != TLOG_RC_OK) {
        fprintf(stderr, "Failed flushing the log: %s\n",
                tlog_grc_strerror(grc));
        if (return_grc == TLOG_RC_OK) {
            return_grc = grc;
        }
        goto cleanup;
    }

cleanup:

    tlog_pkt_cleanup(&pkt);
    /* Stop the timer */
    alarm(0);
    /* Restore signal handlers */
    signal(SIGALRM, SIG_DFL);
    for (i = 0; i < TLOG_ARRAY_SIZE(exit_sig); i++) {
        sigaction(exit_sig[i], NULL, &sa);
        if (sa.sa_handler != SIG_IGN)
            signal(exit_sig[i], SIG_DFL);
    }

    return return_grc;
}
示例#13
0
文件: json_sink.c 项目: Scribery/tlog
static tlog_grc
tlog_json_sink_flush(struct tlog_sink *sink)
{
    struct tlog_json_sink *json_sink = (struct tlog_json_sink *)sink;
    tlog_grc grc;
    char pos_buf[32];
    int len;
    struct timespec pos;

    if (tlog_json_chunk_is_empty(&json_sink->chunk)) {
        return TLOG_RC_OK;
    }

    /* Write terminating metadata records to reserved space */
    tlog_json_chunk_flush(&json_sink->chunk);

    tlog_timespec_sub(&json_sink->chunk.first_ts, &json_sink->start, &pos);

    if (pos.tv_sec == 0) {
        len = snprintf(pos_buf, sizeof(pos_buf), "%ld",
                       pos.tv_nsec / 1000000);
    } else {
        len = snprintf(pos_buf, sizeof(pos_buf), "%lld%03ld",
                       (long long int)pos.tv_sec, pos.tv_nsec / 1000000);
    }

    if ((size_t)len >= sizeof(pos_buf)) {
        return TLOG_GRC_FROM(errno, ENOMEM);
    }

    len = snprintf(
        (char *)json_sink->message_buf, json_sink->message_len,
        "{"
            "\"ver\":"      "\"2.2\","
            "\"host\":"     "\"%s\","
            "\"rec\":"      "\"%s\","
            "\"user\":"     "\"%s\","
            "\"term\":"     "\"%s\","
            "\"session\":"  "%u,"
            "\"id\":"       "%zu,"
            "\"pos\":"      "%s,"
            "\"timing\":"   "\"%.*s\","
            "\"in_txt\":"   "\"%.*s\","
            "\"in_bin\":"   "[%.*s],"
            "\"out_txt\":"  "\"%.*s\","
            "\"out_bin\":"  "[%.*s]"
        "}\n",
        json_sink->hostname,
        json_sink->recording,
        json_sink->username,
        json_sink->terminal,
        json_sink->session_id,
        json_sink->message_id,
        pos_buf,
        (int)(json_sink->chunk.timing_ptr - json_sink->chunk.timing_buf),
        json_sink->chunk.timing_buf,
        (int)json_sink->chunk.input.txt_len, json_sink->chunk.input.txt_buf,
        (int)json_sink->chunk.input.bin_len, json_sink->chunk.input.bin_buf,
        (int)json_sink->chunk.output.txt_len, json_sink->chunk.output.txt_buf,
        (int)json_sink->chunk.output.bin_len, json_sink->chunk.output.bin_buf);
    if (len < 0) {
        return TLOG_RC_FAILURE;
    }
    if ((size_t)len >= json_sink->message_len) {
        return TLOG_GRC_FROM(errno, ENOMEM);
    }

    grc = tlog_json_writer_write(json_sink->writer,
                                 json_sink->message_id,
                                 json_sink->message_buf, len);
    if (grc != TLOG_RC_OK) {
        return grc;
    }

    json_sink->message_id++;
    tlog_json_chunk_empty(&json_sink->chunk);

    return TLOG_RC_OK;
}