1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
|
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <string.h>
#include "vnlog-base64.h"
#define VNLOG_C
#include "vnlog.h"
#define ERR(fmt, ...) do { \
fprintf(stderr, "FATAL ERROR! %s: %s(): " fmt "\n", __FILE__, __func__, ## __VA_ARGS__); \
exit(1); \
} while(0)
void _vnlog_init_session_ctx( struct vnlog_context_t* ctx,
int Nfields);
// VNLOG_N_FIELDS is unknown here so the vnlog_context_t structure has 0
// elements. I dynamically allocate it later with the proper size
static struct vnlog_context_t* get_global_context(int Nfields)
{
static struct vnlog_context_t* ctx;
if(!ctx)
{
if(Nfields < 0)
ERR("Creating a global context not at the start");
ctx = malloc(sizeof(struct vnlog_context_t) +
Nfields * sizeof(ctx->fields[0]));
if(!ctx) ERR("Couldn't allocate context with %d fields", Nfields);
_vnlog_init_session_ctx(ctx, Nfields);
}
return ctx;
}
static void _vnlog_set_output_FILE__ctx_exists(struct vnlog_context_t* ctx, FILE* fp)
{
if(ctx->root->_fp)
ERR("fp is already set");
if( ctx->root->_emitted_something )
ERR("Can only change the output at the start");
ctx->root->_fp = fp;
}
// This is part of the CURRENT ABI. The user calls vnlog_set_output_FILE(), this
// is expanded by the preprocessor to _vnlog_set_output_FILE()
void _vnlog_set_output_FILE(struct vnlog_context_t* ctx, FILE* fp, int Nfields)
{
if( ctx == NULL )
ctx = get_global_context(Nfields);
_vnlog_set_output_FILE__ctx_exists(ctx, fp);
}
// This is for legacy compatibility. With today's vnlog, user calls to
// vnlog_set_output_FILE() are expanded to _vnlog_set_output_FILE() by the
// preprocessor, so THIS symbol is not used, and does not conflict. But old
// binaries could call vnlog_set_output_FILE() directly, so I provide that here,
// with the exact old implementation
void vnlog_set_output_FILE(struct vnlog_context_t* ctx, FILE* fp)
{
_vnlog_set_output_FILE__ctx_exists(ctx, fp);
}
static void check_fp(struct vnlog_context_t* ctx)
{
if(!ctx->root->_fp)
_vnlog_set_output_FILE__ctx_exists(ctx, stdout);
}
static void _emit(struct vnlog_context_t* ctx, const char* string)
{
fprintf(ctx->root->_fp, "%s", string);
ctx->root->_emitted_something = true;
}
static void _emit_field(struct vnlog_context_t* ctx, int i)
{
if( ctx->fields[i].binptr == NULL )
// plain ascii field
_emit(ctx, ctx->fields[i].c);
else
{
// binary field. Encode with base64 first
char out_base64[vnlog_base64_dstlen_to_encode(ctx->fields[i].binlen)];
vnlog_base64_encode( out_base64, sizeof(out_base64),
ctx->fields[i].binptr, ctx->fields[i].binlen );
_emit(ctx, out_base64);
}
}
static void emit(struct vnlog_context_t* ctx, const char* string)
{
check_fp(ctx);
_emit(ctx, string);
}
void _vnlog_printf(struct vnlog_context_t* ctx, int Nfields, const char* fmt, ...)
{
if( ctx == NULL ) ctx = get_global_context(Nfields);
check_fp(ctx);
va_list ap;
va_start(ap, fmt);
vfprintf(ctx->root->_fp, fmt, ap);
va_end(ap);
ctx->root->_emitted_something = true;
}
void _vnlog_flush(struct vnlog_context_t* ctx, int Nfields)
{
if( ctx == NULL ) ctx = get_global_context(Nfields);
check_fp(ctx);
fflush(ctx->root->_fp);
}
static void flush(struct vnlog_context_t* ctx)
{
fflush(ctx->root->_fp);
}
void _vnlog_clear_fields_ctx(struct vnlog_context_t* ctx, int Nfields, bool do_free_binary)
{
ctx->line_has_any_values = false;
for(int i=0; i<Nfields; i++)
{
ctx->fields[i].c[0] = '-';
ctx->fields[i].c[1] = '\0';
// Here the idea is that once I emit the field I deallocate. This is
// inefficient, but allows some things to be simple. For instance I can
// use NULL to recognize empty binary fields. The vast majority of these
// data files will have no binary fields, and this simplicity is good
if(do_free_binary)
free(ctx->fields[i].binptr);
ctx->fields[i].binptr = NULL;
}
}
void _vnlog_init_session_ctx( struct vnlog_context_t* ctx, int Nfields)
{
if( ctx == NULL )
ERR("Can't init a NULL context");
// zero out the context, and set its root to point to itself
*ctx = (struct vnlog_context_t){ .root = ctx };
_vnlog_clear_fields_ctx( ctx, Nfields, false );
}
void _vnlog_init_child_ctx( struct vnlog_context_t* ctx,
const struct vnlog_context_t* ctx_src,
int Nfields)
{
if( ctx == NULL ) ERR("Can't init a NULL context");
if( ctx_src == NULL ) ctx_src = get_global_context(Nfields);
if( !ctx_src->root->_legend_finished )
ERR("Cannot create children contexts before writing a legend. Hard to keep state consistent otherwise.");
// Copy all the context except for the flexible array at the end. The root
// context pointer is copied as well, which is the desired behavior: this
// child has the same root node as the parent
*ctx = *ctx_src;
// reset the flexible array
_vnlog_clear_fields_ctx( ctx, Nfields, false );
}
void _vnlog_free_ctx( struct vnlog_context_t* ctx, int Nfields )
{
for(int i=0; i<Nfields; i++)
{
free(ctx->fields[i].binptr);
ctx->fields[i].binptr = NULL;
}
}
void _vnlog_emit_legend(struct vnlog_context_t* ctx, const char* legend, int Nfields)
{
if( ctx == NULL ) ctx = get_global_context(Nfields);
if( ctx->root->_legend_finished )
ERR("already have a legend");
ctx->root->_legend_finished = true;
emit(ctx, legend);
flush(ctx);
}
static bool is_field_null(const vnlog_field_t* field)
{
return field->binptr == NULL && field->c[0] == '-' && field->c[1] == '\0';
}
static struct vnlog_context_t*
set_field_prelude(struct vnlog_context_t* ctx,
const char* fieldname, int idx)
{
if( ctx == NULL ) ctx = get_global_context(-1);
if(!ctx->root->_legend_finished)
ERR("need a legend to do this");
if(!is_field_null(&ctx->fields[idx]))
ERR("Field '%s' already set. Old value: '%s'",
fieldname, ctx->fields[idx].c);
ctx->line_has_any_values = true;
return ctx;
}
#define DEFINE_SET_FIELD_FUNCTION(type, typename, fmt) \
void \
_vnlog_set_field_value_ ## typename(struct vnlog_context_t* ctx, \
const char* fieldname, int idx, \
type arg) \
{ \
ctx = set_field_prelude(ctx, fieldname, idx); \
if( (int)sizeof(ctx->fields[0].c) <= \
snprintf(ctx->fields[idx].c, sizeof(ctx->fields[0].c), fmt, arg) ) \
{ \
ERR("Field size exceeded for field '%s'", fieldname); \
} \
}
VNLOG_TYPES( DEFINE_SET_FIELD_FUNCTION )
#undef DEFINE_SET_FIELD_FUNCTION
void
_vnlog_set_field_value_binary(struct vnlog_context_t* ctx,
const char* fieldname __attribute__((unused)), int idx,
const void* data, int len)
{
ctx = set_field_prelude(ctx, fieldname, idx);
// Here I allocate the memory for the data, and copy into it. The idea is
// that once I emit the field I deallocate. This is inefficient, but allows
// some things to be simple. For instance I can use NULL to recognize empty
// binary fields. The vast majority of these data files will have no binary
// fields, and this simplicity is good
ctx->fields[idx].binlen = len;
ctx->fields[idx].binptr = realloc(ctx->fields[idx].binptr, len);
memcpy(ctx->fields[idx].binptr, data, len);
}
void _vnlog_emit_record(struct vnlog_context_t* ctx, int Nfields)
{
if( ctx == NULL ) ctx = get_global_context(-1);
if(!ctx->root->_legend_finished)
ERR("need a legend to do this");
if(!ctx->line_has_any_values)
ERR("Tried to emit a log line without any values being set");
check_fp(ctx);
flockfile(ctx->root->_fp);
{
for(int i=0; i<Nfields-1; i++)
{
_emit_field(ctx, i);
_emit(ctx, " ");
}
_emit_field(ctx, Nfields-1);
_emit(ctx, "\n");
}
funlockfile(ctx->root->_fp);
_vnlog_clear_fields_ctx(ctx, Nfields, true);
}
|