• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.04
/src/base/date_time_scanner.cc
1
/**
2
 * Copyright (c) 2020, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 *
29
 * @file date_time_scanner.cc
30
 */
31

32
#include <chrono>
33

34
#include "date_time_scanner.hh"
35

36
#include "config.h"
37
#include "date_time_scanner.cfg.hh"
38
#include "injector.hh"
39
#include "math_util.hh"
40
#include "ptimec.hh"
41
#include "scn/scan.h"
42

43
size_t
44
date_time_scanner::ftime(char* dst,
2,293✔
45
                         size_t len,
46
                         const char* const time_fmt[],
47
                         const exttm& tm) const
48
{
49
    off_t off = 0;
2,293✔
50

51
    if (time_fmt == nullptr || this->dts_fmt_lock == -1
2,293✔
52
        || (tm.et_flags & ETF_MACHINE_ORIENTED))
56✔
53
    {
54
        auto index
2,243✔
55
            = this->dts_fmt_lock != -1 && !(tm.et_flags & ETF_MACHINE_ORIENTED)
2,206✔
56
            ? this->dts_fmt_lock
4,449✔
57
            : PTIMEC_DEFAULT_FMT_INDEX;
58
        PTIMEC_FORMATS[index].pf_ffunc(dst, off, len, tm);
2,243✔
59
        if (tm.et_flags & ETF_SUB_NOT_IN_FORMAT) {
2,243✔
60
            if (tm.et_flags & ETF_MILLIS_SET) {
43✔
61
                dst[off++] = '.';
33✔
62
                ftime_L(dst, off, len, tm);
33✔
63
            } else if (tm.et_flags & ETF_MICROS_SET) {
10✔
64
                dst[off++] = '.';
6✔
65
                ftime_f(dst, off, len, tm);
6✔
66
            } else if (tm.et_flags & ETF_NANOS_SET) {
4✔
67
                dst[off++] = '.';
4✔
68
                ftime_N(dst, off, len, tm);
4✔
69
            }
70
        }
71
        if (index == PTIMEC_DEFAULT_FMT_INDEX && tm.et_flags & ETF_ZONE_SET) {
2,243✔
72
            ftime_z(dst, off, len, tm);
43✔
73
        }
74
        dst[off] = '\0';
2,243✔
75
    } else {
2,243✔
76
        off = ftime_fmt(dst, len, time_fmt[this->dts_fmt_lock], tm);
50✔
77
    }
78

79
    return (size_t) off;
2,293✔
80
}
81

82
bool
83
next_format(const char* const fmt[], int& index, int& locked_index)
7,770,250✔
84
{
85
    bool retval = true;
7,770,250✔
86

87
    if (locked_index == -1) {
7,770,250✔
88
        index += 1;
7,758,430✔
89
        if (fmt[index] == nullptr) {
7,758,430✔
90
            retval = false;
25,179✔
91
        }
92
    } else if (index == locked_index) {
11,820✔
93
        retval = false;
113✔
94
    } else {
95
        index = locked_index;
11,707✔
96
    }
97

98
    return retval;
7,770,250✔
99
}
100

101
const char*
102
date_time_scanner::scan(const char* time_dest,
203,462✔
103
                        size_t time_len,
104
                        const char* const time_fmt[],
105
                        struct exttm* tm_out,
106
                        struct timeval& tv_out,
107
                        bool convert_local)
108
{
109
    static const auto& cfg
110
        = injector::get<const date_time_scanner_ns::config&>();
203,462✔
111

112
    int curr_time_fmt = -1;
203,462✔
113
    bool found = false;
203,462✔
114
    const char* retval = nullptr;
203,462✔
115

116
    if (!time_fmt) {
203,462✔
117
        time_fmt = PTIMEC_FORMAT_STR;
178,787✔
118
    }
119

120
    this->dts_zoned_to_local = cfg.c_zoned_to_local;
203,462✔
121
    while (next_format(time_fmt, curr_time_fmt, this->dts_fmt_lock)) {
7,770,250✔
122
        *tm_out = this->dts_base_tm;
7,744,958✔
123
        tm_out->et_tm.tm_yday = -1;
7,744,958✔
124
        tm_out->et_flags = 0;
7,744,958✔
125
        if (time_len > 1 && time_dest[0] == '+' && isdigit(time_dest[1])) {
7,744,958✔
126
            retval = nullptr;
702✔
127
            auto sv = std::string_view{time_dest, time_len};
702✔
128
            auto epoch_scan_res = scn::scan_value<int64_t>(sv);
702✔
129
            if (epoch_scan_res) {
702✔
130
                time_t gmt = epoch_scan_res->value();
702✔
131

132
                if (convert_local
702✔
133
                    && (this->dts_local_time || this->dts_zoned_to_local))
702✔
134
                {
135
                    localtime_r(&gmt, &tm_out->et_tm);
702✔
136
#ifdef HAVE_STRUCT_TM_TM_ZONE
137
                    tm_out->et_tm.tm_zone = nullptr;
702✔
138
#endif
139
                    tm_out->et_tm.tm_isdst = 0;
702✔
140
                    gmt = tm_out->to_timeval().tv_sec;
702✔
141
                }
142
                tv_out.tv_sec = gmt;
702✔
143
                tv_out.tv_usec = 0;
702✔
144
                tm_out->et_flags = ETF_DAY_SET | ETF_MONTH_SET | ETF_YEAR_SET
702✔
145
                    | ETF_MACHINE_ORIENTED | ETF_EPOCH_TIME | ETF_ZONE_SET;
146

147
                this->dts_fmt_lock = curr_time_fmt;
702✔
148
                this->dts_fmt_len = sv.length() - epoch_scan_res->range().size();
702✔
149
                retval = time_dest + this->dts_fmt_len;
702✔
150
                found = true;
702✔
151
                break;
702✔
152
            }
153
        } else if (time_fmt == PTIMEC_FORMAT_STR) {
7,744,256✔
154
            ptime_func func = PTIMEC_FORMATS[curr_time_fmt].pf_func;
7,713,808✔
155
            off_t off = 0;
7,713,808✔
156

157
#ifdef HAVE_STRUCT_TM_TM_ZONE
158
            if (!this->dts_keep_base_tz) {
7,713,808✔
159
                tm_out->et_tm.tm_zone = nullptr;
7,713,808✔
160
            }
161
#endif
162
            if (func(tm_out, time_dest, off, time_len)) {
7,713,808✔
163
                retval = &time_dest[off];
152,892✔
164

165
                if (tm_out->et_tm.tm_year < 70) {
152,892✔
166
                    tm_out->et_tm.tm_year = 80;
13,299✔
167
                }
168
                if (convert_local
152,892✔
169
                    && (this->dts_local_time
152,771✔
170
                        || tm_out->et_flags & ETF_EPOCH_TIME
152,675✔
171
                        || ((tm_out->et_flags & ETF_ZONE_SET
152,662✔
172
                             || this->dts_default_zone != nullptr)
63,822✔
173
                            && this->dts_zoned_to_local)))
88,929✔
174
                {
175
                    time_t gmt = tm_out->to_timeval().tv_sec;
89,024✔
176

177
                    if (!(tm_out->et_flags & ETF_ZONE_SET)
89,024✔
178
                        && !(tm_out->et_flags & ETF_EPOCH_TIME)
89✔
179
                        && this->dts_default_zone != nullptr)
89✔
180
                    {
181
                        try {
182
                            date::local_seconds stime;
89✔
183
                            stime += std::chrono::seconds{gmt};
89✔
184
                            auto ztime
185
                                = date::make_zoned(this->dts_default_zone,
89✔
186
                                                   stime,
187
                                                   date::choose::earliest);
188
                            gmt = std::chrono::duration_cast<
89✔
189
                                      std::chrono::seconds>(
89✔
190
                                      ztime.get_sys_time().time_since_epoch())
178✔
191
                                      .count();
89✔
192
                        } catch (const std::exception& e) {
×
UNCOV
193
                            log_error("failed to convert time %d -- %s",
×
194
                                      gmt,
195
                                      e.what());
UNCOV
196
                        }
×
197
                    }
198
                    this->to_localtime(gmt, *tm_out);
89,024✔
199
                }
200
                const auto& last_tm = this->dts_last_tm;
152,892✔
201
                if (last_tm.tm_year == tm_out->et_tm.tm_year
152,892✔
202
                    && last_tm.tm_mon == tm_out->et_tm.tm_mon
6,986✔
203
                    && last_tm.tm_mday == tm_out->et_tm.tm_mday
6,972✔
204
                    && last_tm.tm_hour == tm_out->et_tm.tm_hour
6,965✔
205
                    && last_tm.tm_min == tm_out->et_tm.tm_min)
6,589✔
206
                {
207
                    const auto sec_diff = tm_out->et_tm.tm_sec - last_tm.tm_sec;
6,266✔
208

209
                    tv_out = this->dts_last_tv;
6,266✔
210
                    tv_out.tv_sec += sec_diff;
6,266✔
211
                    tm_out->et_tm.tm_wday = last_tm.tm_wday;
6,266✔
212
                } else {
6,266✔
213
                    tv_out = tm_out->to_timeval();
146,626✔
214
                    secs2wday(tv_out, &tm_out->et_tm);
146,626✔
215
                }
216
                tv_out.tv_usec = tm_out->et_nsec / 1000;
152,892✔
217

218
                this->dts_fmt_lock = curr_time_fmt;
152,892✔
219
                this->dts_fmt_len = retval - time_dest;
152,892✔
220

221
                found = true;
152,892✔
222
                break;
152,892✔
223
            }
224
        } else {
225
            off_t off = 0;
30,448✔
226

227
#ifdef HAVE_STRUCT_TM_TM_ZONE
228
            if (!this->dts_keep_base_tz) {
30,448✔
229
                tm_out->et_tm.tm_zone = nullptr;
30,448✔
230
            }
231
#endif
232
            if (ptime_fmt(
91,344✔
233
                    time_fmt[curr_time_fmt], tm_out, time_dest, off, time_len)
30,448✔
234
                && (time_dest[off] == '.' || time_dest[off] == ','
54,433✔
235
                    || off == (off_t) time_len))
23,985✔
236
            {
237
                retval = &time_dest[off];
24,576✔
238
                if (tm_out->et_tm.tm_year < 70) {
24,576✔
239
                    tm_out->et_tm.tm_year = 80;
11,090✔
240
                }
241
                if (convert_local
24,576✔
242
                    && (this->dts_local_time
24,576✔
243
                        || tm_out->et_flags & ETF_EPOCH_TIME
24,564✔
244
                        || ((tm_out->et_flags & ETF_ZONE_SET
15,457✔
245
                             || this->dts_default_zone != nullptr)
15,457✔
246
                            && this->dts_zoned_to_local)))
55✔
247
                {
248
                    time_t gmt = tm_out->to_timeval().tv_sec;
9,174✔
249

250
                    if (!(tm_out->et_flags & ETF_ZONE_SET)
9,174✔
251
                        && !(tm_out->et_flags & ETF_EPOCH_TIME)
55✔
252
                        && this->dts_default_zone != nullptr)
55✔
253
                    {
254
                        date::local_seconds stime;
55✔
255
                        stime += std::chrono::seconds{gmt};
55✔
256
                        auto ztime
257
                            = date::make_zoned(this->dts_default_zone, stime);
55✔
258
                        gmt = std::chrono::duration_cast<std::chrono::seconds>(
55✔
259
                                  ztime.get_sys_time().time_since_epoch())
110✔
260
                                  .count();
55✔
261
                    }
262
                    this->to_localtime(gmt, *tm_out);
9,174✔
263
                }
264
                const auto& last_tm = this->dts_last_tm;
24,576✔
265
                if (last_tm.tm_year == tm_out->et_tm.tm_year
24,576✔
266
                    && last_tm.tm_mon == tm_out->et_tm.tm_mon
4,192✔
267
                    && last_tm.tm_mday == tm_out->et_tm.tm_mday
4,192✔
268
                    && last_tm.tm_hour == tm_out->et_tm.tm_hour
4,192✔
269
                    && last_tm.tm_min == tm_out->et_tm.tm_min)
4,192✔
270
                {
271
                    const auto sec_diff = tm_out->et_tm.tm_sec - last_tm.tm_sec;
4,117✔
272

273
                    tv_out = this->dts_last_tv;
4,117✔
274
                    tv_out.tv_sec += sec_diff;
4,117✔
275
                    tm_out->et_tm.tm_wday = last_tm.tm_wday;
4,117✔
276
                } else {
4,117✔
277
                    tv_out = tm_out->to_timeval();
20,459✔
278
                    secs2wday(tv_out, &tm_out->et_tm);
20,459✔
279
                }
280
                tv_out.tv_usec = tm_out->et_nsec / 1000;
24,576✔
281

282
                this->dts_fmt_lock = curr_time_fmt;
24,576✔
283
                this->dts_fmt_len = retval - time_dest;
24,576✔
284

285
                found = true;
24,576✔
286
                break;
24,576✔
287
            }
288
        }
289
    }
290

291
    if (!found) {
203,462✔
292
        retval = nullptr;
25,292✔
293
    }
294

295
    if (retval != nullptr) {
203,462✔
296
        this->dts_last_tm = tm_out->et_tm;
178,170✔
297
        this->dts_last_tv = tv_out;
178,170✔
298
    }
299

300
    if (retval != nullptr && static_cast<size_t>(retval - time_dest) < time_len)
203,462✔
301
    {
302
        /* Try to pull out the milli/micro-second value. */
303
        if (!(tm_out->et_flags
12,395✔
304
              & (ETF_MILLIS_SET | ETF_MICROS_SET | ETF_NANOS_SET))
12,395✔
305
            && (retval[0] == '.' || retval[0] == ','))
12,332✔
306
        {
307
            off_t off = (retval - time_dest) + 1;
11,462✔
308

309
            if (ptime_N(tm_out, time_dest, off, time_len)) {
11,462✔
310
                tv_out.tv_usec
311
                    = std::chrono::duration_cast<std::chrono::microseconds>(
×
312
                          std::chrono::nanoseconds{tm_out->et_nsec})
×
313
                          .count();
×
314
                this->dts_fmt_len += 10;
×
315
                tm_out->et_flags |= ETF_NANOS_SET | ETF_SUB_NOT_IN_FORMAT;
×
UNCOV
316
                retval += 10;
×
317
            } else if (ptime_f(tm_out, time_dest, off, time_len)) {
11,462✔
318
                tv_out.tv_usec
319
                    = std::chrono::duration_cast<std::chrono::microseconds>(
11,462✔
320
                          std::chrono::nanoseconds{tm_out->et_nsec})
11,462✔
321
                          .count();
11,462✔
322
                this->dts_fmt_len = off;
11,462✔
323
                tm_out->et_flags |= ETF_SUB_NOT_IN_FORMAT;
11,462✔
324
                retval = time_dest + this->dts_fmt_len;
11,462✔
325
            }
326
        }
327
    }
328

329
    return retval;
203,462✔
330
}
331

332
void
333
date_time_scanner::clear()
1,754,110✔
334
{
335
    this->dts_base_time = 0;
1,754,110✔
336
    this->dts_base_tm = exttm{};
1,754,110✔
337
    this->dts_fmt_lock = -1;
1,754,110✔
338
    this->dts_fmt_len = -1;
1,754,110✔
339
    this->dts_last_tv = timeval{};
1,754,110✔
340
    this->dts_last_tm = tm{};
1,754,110✔
341
    this->dts_localtime_cached_gmt = 0;
1,754,110✔
342
    this->dts_localtime_cached_tm = tm{};
1,754,110✔
343
}
1,754,110✔
344

345
void
346
date_time_scanner::set_base_time(time_t base_time, const tm& local_tm)
733,206✔
347
{
348
    this->dts_base_time = base_time;
733,206✔
349
    this->dts_base_tm.et_tm = local_tm;
733,206✔
350
    this->dts_last_tm = tm{};
733,206✔
351
    this->dts_last_tv = timeval{};
733,206✔
352
}
733,206✔
353

354
void
355
date_time_scanner::to_localtime(time_t t, exttm& tm_out)
98,258✔
356
{
357
    if (t < (24 * 60 * 60)) {
98,258✔
358
        // Don't convert and risk going past the epoch.
359
        return;
1✔
360
    }
361

362
    if (t < this->dts_local_offset_valid || t >= this->dts_local_offset_expiry)
98,257✔
363
    {
364
        localtime_r(&t, &tm_out.et_tm);
88,571✔
365
        // Clear the gmtoff set by localtime_r() otherwise tm2sec() will
366
        // convert the time back again.
367
#ifdef HAVE_STRUCT_TM_TM_ZONE
368
        tm_out.et_tm.tm_gmtoff = 0;
88,571✔
369
        tm_out.et_tm.tm_zone = nullptr;
88,571✔
370
#endif
371
        tm_out.et_tm.tm_isdst = 0;
88,571✔
372
        auto new_gmt = tm2sec(&tm_out.et_tm);
88,571✔
373
        this->dts_local_offset_cache = new_gmt - t;
88,571✔
374
        this->dts_local_offset_valid = t;
88,571✔
375
        this->dts_local_offset_expiry = t + (EXPIRE_TIME - 1);
88,571✔
376
        this->dts_local_offset_expiry
88,571✔
377
            -= this->dts_local_offset_expiry % EXPIRE_TIME;
88,571✔
378
    } else {
88,571✔
379
        time_t adjust_gmt = t + this->dts_local_offset_cache;
9,686✔
380
        auto adjust_gmt_min = adjust_gmt / 60;
9,686✔
381
        if (this->dts_localtime_cached_gmt == adjust_gmt_min)
9,686✔
382
        {
383
            tm_out.et_tm = this->dts_localtime_cached_tm;
8,554✔
384
            tm_out.et_tm.tm_sec = adjust_gmt % 60;
8,554✔
385
        } else {
386
            secs2tm(adjust_gmt, &tm_out.et_tm);
1,132✔
387
            this->dts_localtime_cached_gmt = adjust_gmt_min;
1,132✔
388
            this->dts_localtime_cached_tm = tm_out.et_tm;
1,132✔
389
            this->dts_localtime_cached_tm.tm_sec = 0;
1,132✔
390
        }
391
#if 0
392
        {
393
            tm verify_tm;
394
            secs2tm(adjust_gmt, &verify_tm);
395
            require(tm_out.et_tm.tm_year == verify_tm.tm_year);
396
            require(tm_out.et_tm.tm_mon == verify_tm.tm_mon);
397
            require(tm_out.et_tm.tm_mday == verify_tm.tm_mday);
398
            require(tm_out.et_tm.tm_hour == verify_tm.tm_hour);
399
            require(tm_out.et_tm.tm_min == verify_tm.tm_min);
400
            require(tm_out.et_tm.tm_sec == verify_tm.tm_sec);
401
        }
402
#endif
403
    }
404
    tm_out.et_gmtoff = 0;
98,257✔
405
#ifdef HAVE_STRUCT_TM_TM_ZONE
406
    tm_out.et_tm.tm_gmtoff = 0;
98,257✔
407
    tm_out.et_tm.tm_zone = nullptr;
98,257✔
408
#endif
409
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc