Coverage Report

Created: 2025-11-11 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/S2OPC/src/Common/helpers/sopc_log_manager.c
Line
Count
Source
1
/*
2
 * Licensed to Systerel under one or more contributor license
3
 * agreements. See the NOTICE file distributed with this work
4
 * for additional information regarding copyright ownership.
5
 * Systerel licenses this file to you under the Apache
6
 * License, Version 2.0 (the "License"); you may not use this
7
 * file except in compliance with the License. You may obtain
8
 * a copy of the License at
9
 *
10
 *   http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
20
#include "sopc_log_manager.h"
21
22
#include <stdarg.h>
23
#include <stdbool.h>
24
#include <stdio.h>
25
#include <string.h>
26
27
#include "sopc_assert.h"
28
#include "sopc_circular_log_file.h"
29
#include "sopc_common_constants.h"
30
#include "sopc_date_time.h"
31
#include "sopc_macros.h"
32
#include "sopc_mem_alloc.h"
33
#include "sopc_mutexes.h"
34
35
// Keep 100 bytes to print log file change
36
#define RESERVED_BYTES_PRINT_FILE_CHANGE 100
37
0
#define CATEGORY_MAX_LENGTH 9
38
39
static void trace_Internal(SOPC_Log_Instance* pLogInst, SOPC_Log_Level level, bool always, const char* format, ...);
40
static void vTrace_Internal(SOPC_Log_Instance* pLogInst,
41
                            SOPC_Log_Level level,
42
                            bool always,
43
                            const char* format,
44
                            va_list args);
45
46
const char* SOPC_CSTRING_LEVEL_ERROR = "(Error) ";
47
const char* SOPC_CSTRING_LEVEL_WARNING = "(Warning) ";
48
const char* SOPC_CSTRING_LEVEL_INFO = "";
49
const char* SOPC_CSTRING_LEVEL_DEBUG = "(Debug) ";
50
const char* SOPC_CSTRING_LEVEL_UNKNOWN = "(?) ";
51
52
struct SOPC_Log_Instance
53
{
54
    // 'actual' points to the actual instance. If not NULL, only category, started and level are relevant
55
    SOPC_Log_Instance* actual;
56
    char category[CATEGORY_MAX_LENGTH + 1]; // + 1 for NULL termination
57
    SOPC_Log_Level level;
58
    bool started;
59
    bool consoleFlag;
60
    // The Following fields are only used in an actual instance. (When actual is NULL)
61
    SOPC_Mutex mutex;
62
    uint8_t nbRefs;
63
    SOPC_CircularLogFile* pFile;     // NULL if not used
64
    SOPC_Log_UserDoLog* logCallback; // NULL if not used
65
    uint32_t logPosition;            // current position in callbackBuffer
66
    char* printBuffer;
67
};
68
69
static const char* levelToString(const SOPC_Log_Level level)
70
0
{
71
0
    switch ((int) level)
72
0
    {
73
0
    case SOPC_LOG_LEVEL_ERROR:
74
0
        return SOPC_CSTRING_LEVEL_ERROR;
75
0
    case SOPC_LOG_LEVEL_WARNING:
76
0
        return SOPC_CSTRING_LEVEL_WARNING;
77
0
    case SOPC_LOG_LEVEL_INFO:
78
0
        return SOPC_CSTRING_LEVEL_INFO;
79
0
    case SOPC_LOG_LEVEL_DEBUG:
80
0
        return SOPC_CSTRING_LEVEL_DEBUG;
81
0
    default:
82
0
        return SOPC_CSTRING_LEVEL_UNKNOWN;
83
0
    }
84
0
}
85
86
// Print starting timestamp
87
static bool SOPC_Log_Start(SOPC_Log_Instance* pLogInst)
88
0
{
89
0
    bool result = false;
90
0
    if (NULL != pLogInst && !pLogInst->started)
91
0
    {
92
0
        SOPC_Log_Instance* actual = (pLogInst->actual == NULL ? pLogInst : pLogInst->actual);
93
94
0
        SOPC_Mutex_Lock(&actual->mutex);
95
        // Note: check again the flag to avoid possible concurrent assignment detection by static analysis.
96
        //       Nevertheless since caller is logInst creation function, it should never occur.
97
0
        if (!pLogInst->started)
98
0
        {
99
0
            if ((NULL != actual->pFile) || (NULL != actual->logCallback))
100
0
            {
101
0
                pLogInst->started = true;
102
0
                trace_Internal(pLogInst, SOPC_LOG_LEVEL_INFO, true, "LOG START");
103
0
                result = true;
104
0
            }
105
0
            else
106
0
            {
107
0
                SOPC_CONSOLE_PRINTF("Log error: impossible to write in NULL stream.\n");
108
0
            }
109
0
        }
110
0
        SOPC_Mutex_Unlock(&actual->mutex);
111
0
    }
112
0
    return result;
113
0
}
114
115
// Fill pLogInst->category with category, aligned and truncated to CATEGORY_MAX_LENGTH
116
static void SOPC_Log_AlignCategory(const char* category, SOPC_Log_Instance* pLogInst)
117
0
{
118
0
    if (NULL == pLogInst)
119
0
    {
120
0
        return;
121
0
    }
122
0
    if (NULL != category)
123
0
    {
124
0
        const size_t category_len = strlen(category);
125
0
        if (category_len > 0 && category_len <= CATEGORY_MAX_LENGTH)
126
0
        {
127
0
            memcpy(pLogInst->category, category, category_len);
128
0
            pLogInst->category[category_len] = '\0';
129
0
        }
130
0
        else
131
0
        {
132
0
            memcpy(pLogInst->category, category, CATEGORY_MAX_LENGTH);
133
0
            pLogInst->category[CATEGORY_MAX_LENGTH] = '\0';
134
0
        }
135
0
    }
136
0
    else
137
0
    {
138
0
        pLogInst->category[0] = '\0';
139
0
    }
140
0
}
141
142
SOPC_Log_Instance* SOPC_Log_CreateUserInstance(const char* category, SOPC_Log_UserDoLog* logCallback)
143
0
{
144
0
    SOPC_Log_Instance* result = SOPC_Calloc(1, sizeof(SOPC_Log_Instance));
145
146
0
    if (NULL == result)
147
0
    {
148
0
        return NULL;
149
0
    }
150
151
    // Fill fields
152
0
    SOPC_Log_AlignCategory(category, result);
153
0
    result->logCallback = logCallback;
154
0
    result->nbRefs = 1;
155
0
    result->printBuffer = SOPC_Malloc(SOPC_LOG_MAX_USER_LINE_LENGTH + 1); // + NULL
156
0
    SOPC_ReturnStatus mutex_res = SOPC_Mutex_Initialization(&result->mutex);
157
0
    SOPC_ASSERT(SOPC_STATUS_OK == mutex_res);
158
159
    // All other fields are initialized to 0 with Calloc
160
161
0
    if (NULL != result->printBuffer)
162
0
    {
163
        // Starts the log instance
164
0
        bool started = SOPC_Log_Start(result);
165
166
0
        if (!started)
167
0
        {
168
0
            SOPC_Log_ClearInstance(&result);
169
0
        }
170
0
    }
171
0
    else
172
0
    {
173
        /* Allocation of callbackBuffer failed */
174
0
        SOPC_Mutex_Clear(&result->mutex);
175
0
        SOPC_Free(result);
176
0
        result = NULL;
177
0
    }
178
179
0
    return result;
180
0
}
181
182
SOPC_Log_Instance* SOPC_Log_CreateFileInstance(const SOPC_CircularLogFile_Configuration* pConf, const char* category)
183
0
{
184
0
    SOPC_Log_Instance* result = SOPC_Calloc(1, sizeof(SOPC_Log_Instance));
185
186
0
    if (NULL == result)
187
0
    {
188
0
        return NULL;
189
0
    }
190
191
0
    SOPC_CircularLogFile* pFile = SOPC_CircularLogFile_Create(pConf);
192
193
0
    if (NULL == pFile)
194
0
    {
195
0
        SOPC_Free(result);
196
0
        SOPC_CircularLogFile_Delete(&pFile);
197
0
        return NULL;
198
0
    }
199
200
    // Fill fields
201
0
    SOPC_Log_AlignCategory(category, result);
202
0
    result->printBuffer = SOPC_Malloc(SOPC_LOG_MAX_USER_LINE_LENGTH + 1); // + NULL
203
0
    SOPC_ASSERT(NULL != result->printBuffer);
204
0
    result->pFile = pFile;
205
0
    result->nbRefs = 1;
206
0
    SOPC_ReturnStatus mutex_res = SOPC_Mutex_Initialization(&result->mutex);
207
0
    SOPC_ASSERT(SOPC_STATUS_OK == mutex_res);
208
    // All other fields are initialized to 0 with Calloc
209
210
    // Starts the log instance
211
0
    bool started = SOPC_Log_Start(result);
212
0
    if (!started)
213
0
    {
214
0
        SOPC_CircularLogFile_Delete(&pFile);
215
0
        SOPC_Mutex_Clear(&result->mutex);
216
0
        SOPC_Free(result);
217
0
        result = NULL;
218
0
    }
219
0
    return result;
220
0
}
221
222
SOPC_Log_Instance* SOPC_Log_CreateInstanceAssociation(SOPC_Log_Instance* pLogInst, const char* category)
223
0
{
224
0
    SOPC_Log_Instance* result = NULL;
225
226
0
    if (NULL != pLogInst)
227
0
    {
228
0
        result = SOPC_Calloc(1, sizeof(SOPC_Log_Instance));
229
230
0
        if (result != NULL)
231
0
        {
232
0
            SOPC_Log_Instance* actual = (pLogInst->actual == NULL ? pLogInst : pLogInst->actual);
233
0
            SOPC_Mutex_Lock(&actual->mutex);
234
0
            SOPC_ASSERT(actual->nbRefs < UINT8_MAX);
235
0
            {
236
                // This is a linked instance. Only category and level are setup.
237
0
                result->actual = actual;
238
0
                result->level = SOPC_LOG_LEVEL_ERROR;
239
0
                actual->nbRefs++;
240
0
                SOPC_Log_AlignCategory(category, result);
241
0
            }
242
0
            SOPC_Mutex_Unlock(&actual->mutex);
243
244
0
            bool started = SOPC_Log_Start(result);
245
246
0
            if (!started)
247
0
            {
248
0
                SOPC_Log_ClearInstance(&result);
249
0
            }
250
0
        }
251
0
    }
252
0
    return result;
253
0
}
254
255
// Define the log level active (all traces <= level are displayed)
256
bool SOPC_Log_SetLogLevel(SOPC_Log_Instance* pLogInst, SOPC_Log_Level level)
257
0
{
258
0
    bool result = false;
259
0
    if (NULL != pLogInst && pLogInst->started)
260
0
    {
261
0
        SOPC_Log_Instance* actual = (pLogInst->actual == NULL ? pLogInst : pLogInst->actual);
262
0
        const char* levelName = "";
263
0
        result = true;
264
265
0
        switch (level)
266
0
        {
267
0
        case SOPC_LOG_LEVEL_ERROR:
268
0
            levelName = "ERROR";
269
0
            break;
270
0
        case SOPC_LOG_LEVEL_WARNING:
271
0
            levelName = "WARNING";
272
0
            break;
273
0
        case SOPC_LOG_LEVEL_INFO:
274
0
            levelName = "INFO";
275
0
            break;
276
0
        case SOPC_LOG_LEVEL_DEBUG:
277
0
            levelName = "DEBUG";
278
0
            break;
279
0
        default:
280
0
            result = false;
281
0
            break;
282
0
        }
283
0
        if (result)
284
0
        {
285
0
            SOPC_Mutex_Lock(&actual->mutex);
286
0
            pLogInst->level = level;
287
288
0
            trace_Internal(pLogInst, SOPC_LOG_LEVEL_INFO, true, "LOG LEVEL SET TO '%s'", levelName);
289
0
            SOPC_Mutex_Unlock(&actual->mutex);
290
0
        }
291
0
    }
292
0
    return result;
293
0
}
294
295
SOPC_Log_Level SOPC_Log_GetLogLevel(SOPC_Log_Instance* pLogInst)
296
0
{
297
0
    SOPC_Log_Level level = SOPC_LOG_LEVEL_ERROR;
298
0
    if (NULL != pLogInst)
299
0
    {
300
0
        level = pLogInst->level;
301
0
    }
302
0
    return level;
303
0
}
304
305
// Activate output in console
306
bool SOPC_Log_SetConsoleOutput(SOPC_Log_Instance* pLogInst, bool activate)
307
0
{
308
0
    bool result = false;
309
0
    if (NULL != pLogInst && pLogInst->started)
310
0
    {
311
0
        SOPC_Log_Instance* actual = (pLogInst->actual == NULL ? pLogInst : pLogInst->actual);
312
0
        SOPC_Mutex_Lock(&actual->mutex);
313
0
        pLogInst->consoleFlag = activate;
314
0
        result = true;
315
316
0
        trace_Internal(pLogInst, SOPC_LOG_LEVEL_INFO, true, "LOG CONSOLE OUTPUT SET TO '%s'",
317
0
                       activate ? "TRUE" : "FALSE");
318
0
        SOPC_Mutex_Unlock(&actual->mutex);
319
0
    }
320
0
    return result;
321
0
}
322
323
char* SOPC_Log_GetCurrentFilename(const SOPC_Log_Instance* pLogInst)
324
0
{
325
0
    char* result = NULL;
326
0
    if (NULL != pLogInst)
327
0
    {
328
0
        const SOPC_Log_Instance* actual = (pLogInst->actual == NULL ? pLogInst : pLogInst->actual);
329
0
        result = SOPC_CircularLogFile_GetFileName(actual->pFile);
330
0
    }
331
0
    return result;
332
0
}
333
334
static void trace_Internal(SOPC_Log_Instance* pLogInst, SOPC_Log_Level level, bool always, const char* format, ...)
335
0
{
336
0
    va_list args;
337
0
    va_start(args, format);
338
0
    vTrace_Internal(pLogInst, level, always, format, args);
339
0
    va_end(args);
340
0
}
341
342
SOPC_STRING_FORMAT(4)
343
static void vTrace_Internal(SOPC_Log_Instance* pLogInst,
344
                            SOPC_Log_Level level,
345
                            bool always,
346
                            const char* format,
347
                            va_list args)
348
0
{
349
0
    if (NULL != pLogInst && ((pLogInst->started && level <= pLogInst->level) || always))
350
0
    {
351
0
        SOPC_Log_Instance* actual = (pLogInst->actual == NULL ? pLogInst : pLogInst->actual);
352
0
        SOPC_Mutex_Lock(&actual->mutex);
353
354
        // Format full line
355
356
0
        char* timestamp = SOPC_Time_GetStringOfCurrentTimeUTC(false);
357
0
        int remain = SOPC_LOG_MAX_USER_LINE_LENGTH + 1;
358
0
        SOPC_ASSERT(NULL != actual->printBuffer);
359
360
0
        const bool isFile = (NULL != actual->pFile);
361
362
        // Reminder : actual->printBuffer has a size of (SOPC_LOG_MAX_USER_LINE_LENGTH + 1)
363
0
        int pos = 0;
364
0
        if (isFile)
365
0
        {
366
            // In files, the log line is as follow (<LEVEL> skipped for INFO level):
367
            // [YYYY/MM/DD HH:MM:SS.sss] <Category> <LEVEL > <Log>
368
0
            pos = snprintf(actual->printBuffer, SOPC_LOG_MAX_USER_LINE_LENGTH + 1, "[%s] %s %s", timestamp,
369
0
                           pLogInst->category, levelToString(level));
370
0
            remain -= pos;
371
0
        }
372
373
0
        if (remain > 0)
374
0
        {
375
0
            vsnprintf(actual->printBuffer + pos, (size_t) remain, format, args);
376
0
        }
377
0
        actual->printBuffer[SOPC_LOG_MAX_USER_LINE_LENGTH] = 0;
378
379
        // File
380
0
        if (isFile)
381
0
        {
382
0
            SOPC_CircularLogFile_PutLine(actual->pFile, actual->printBuffer);
383
384
            // Write line in dedicated sections
385
0
            if (pLogInst->consoleFlag)
386
0
            {
387
0
                SOPC_CONSOLE_PRINTF("%s\n", actual->printBuffer);
388
0
            }
389
0
        }
390
391
        // User
392
0
        if (NULL != actual->logCallback)
393
0
        {
394
            // In user log system, there is no "\n" added.
395
0
            actual->logCallback(timestamp, pLogInst->category, level, actual->printBuffer);
396
0
        }
397
398
0
        SOPC_Free(timestamp);
399
0
        *actual->printBuffer = 0;
400
401
0
        SOPC_Mutex_Unlock(&actual->mutex);
402
0
    }
403
0
}
404
405
SOPC_STRING_FORMAT(3)
406
void SOPC_Log_VTrace(SOPC_Log_Instance* pLogInst, SOPC_Log_Level level, const char* format, va_list args)
407
0
{
408
0
    vTrace_Internal(pLogInst, level, false, format, args);
409
0
}
410
411
// Print new trace in log file (and console if applicable)
412
SOPC_STRING_FORMAT(3)
413
void SOPC_Log_Trace(SOPC_Log_Instance* pLogInst, SOPC_Log_Level level, const char* format, ...)
414
0
{
415
0
    va_list args;
416
0
    va_start(args, format);
417
0
    SOPC_Log_VTrace(pLogInst, level, format, args);
418
0
    va_end(args);
419
0
}
420
421
// Deallocate the Log instance structure
422
void SOPC_Log_ClearInstance(SOPC_Log_Instance** ppLogInst)
423
0
{
424
0
    SOPC_Log_Instance* pLogInst = NULL;
425
0
    if (ppLogInst != NULL && *ppLogInst != NULL)
426
0
    {
427
0
        pLogInst = *ppLogInst;
428
0
        SOPC_Log_Instance* actual = (pLogInst->actual == NULL ? pLogInst : pLogInst->actual);
429
430
0
        SOPC_Mutex_Lock(&actual->mutex);
431
0
        if (pLogInst->started)
432
0
        {
433
0
            trace_Internal(pLogInst, SOPC_LOG_LEVEL_INFO, true, "LOG STOP");
434
0
        }
435
436
        // Only allow to delete "actual" session if there are no remaining references
437
0
        SOPC_ASSERT(pLogInst->actual != NULL || actual->nbRefs == 1);
438
439
0
        if (actual->nbRefs <= 1)
440
0
        {
441
            // Last reference is always the actual session
442
0
            if (pLogInst->actual != NULL)
443
0
            {
444
0
                SOPC_CONSOLE_PRINTF("Cannot delete instance file before associated ones!\n");
445
0
                SOPC_Log_Trace(pLogInst, SOPC_LOG_LEVEL_ERROR, "Cannot delete instance file before associated ones!");
446
0
                SOPC_ASSERT(false);
447
0
            }
448
449
            // There are no more instances.
450
0
            if (NULL != pLogInst->pFile)
451
0
            {
452
0
                SOPC_CircularLogFile_Delete(&pLogInst->pFile);
453
0
            }
454
455
0
            SOPC_Free(pLogInst->printBuffer);
456
457
0
            SOPC_Mutex_Unlock(&actual->mutex);
458
0
            SOPC_Mutex_Clear(&actual->mutex);
459
0
        }
460
0
        else
461
0
        {
462
0
            actual->nbRefs--;
463
0
            SOPC_Mutex_Unlock(&actual->mutex);
464
0
        }
465
0
        SOPC_Free(pLogInst);
466
        *ppLogInst = NULL;
467
0
    }
468
0
}