Skip to content

Commit 6d54505

Browse files
authored
Loki: add __line__ and __timestamp__ to label_format (grafana#6983)
<!-- Thanks for sending a pull request! Before submitting: 1. Read our CONTRIBUTING.md guide 2. Name your PR as `<Feature Area>: Describe your change`. a. Do not end the title with punctuation. It will be added in the changelog. b. Start with an imperative verb. Example: Fix the latency between System A and System B. c. Use sentence case, not title case. d. Use a complete phrase or sentence. The PR title will appear in a changelog, so help other people understand what your change will be. 3. Rebase your PR if it gets out of sync with main --> **What this PR does / why we need it**: We have the `__line__` and `__timestamp__` functions currently available in the `line_format` stage but not in the `label_format` stage. This PR fixes that. Specifically `__timestamp__` can be valuable in a label because you can do things like ``` label_format hour_of_day=`{{__timestamp__ | date "15"}}` ``` To be able to conditionaly select log lines based on the hour of the day they were created, this can be useful for conditional alerting as an example. **Which issue(s) this PR fixes**: Fixes grafana#6402 **Special notes for your reviewer**: <!-- Note about CHANGELOG entries, if a change adds: * an important feature * fixes an issue present in a previous release, * causes a change in operation that would be useful for an operator of Loki to know then please add a CHANGELOG entry. For documentation changes, build changes, simple fixes etc please skip this step. We are attempting to curate a changelog of the most relevant and important changes to be easier to ingest by end users of Loki. Note about the upgrade guide, if this changes: * default configuration values * metric names or label names * changes existing log lines such as the metrics.go query output line * configuration parameters * anything to do with any API * any other change that would require special attention or extra steps to upgrade Please document clearly what changed AND what needs to be done in the upgrade guide. --> **Checklist** - [x] Documentation added - [ ] Tests updated - [x] Is this an important fix or new feature? Add an entry in the `CHANGELOG.md`. - [ ] Changes that require user attention or interaction to upgrade are documented in `docs/sources/upgrading/_index.md` Signed-off-by: Edward Welch <[email protected]>
1 parent 4a21923 commit 6d54505

File tree

4 files changed

+73
-17
lines changed

4 files changed

+73
-17
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#### Loki
66

77
##### Enhancements
8+
* [6983](https://github.com/grafana/loki/pull/6983) **slim-bean**: `__timestamp__` and `__line__` are now available in the logql `label_format` query stage.
89
* [6821](https://github.com/grafana/loki/pull/6821) **kavirajk**: Introduce new cache type `embedded-cache` which is an in-process cache system that runs loki without the need for an external cache (like memcached, redis, etc). It can be run in two modes `distributed: false` (default, and same as old `fifocache`) and `distributed: true` which runs cache in distributed fashion sharding keys across peers if Loki is run in microservices or SSD mode.
910
* [6691](https://github.com/grafana/loki/pull/6691) **dannykopping**: Update production-ready Loki cluster in docker-compose
1011
* [6317](https://github.com/grafana/loki/pull/6317) **dannykoping**: General: add cache usage statistics

docs/sources/logql/template_functions.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ All labels are added as variables in the template engine. They can be referenced
1313
{{ .path }}
1414
```
1515

16-
Additionally you can also access the log line using the [`__line__`](#__line__) function.
16+
Additionally you can also access the log line using the [`__line__`](#__line__) function and the timestamp using the [`__timestamp__`](#__timestamp__) function.
1717

1818
You can take advantage of [pipeline](https://golang.org/pkg/text/template/#hdr-Pipelines) to join together multiple functions.
1919
In a chained pipeline, the result of each command is passed as the last argument of the following command.

pkg/logql/log/fmt.go

+40-15
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ var (
9090
}
9191
)
9292

93+
func addLineAndTimestampFunctions(currLine func() string, currTimestamp func() int64) map[string]interface{} {
94+
functions := make(map[string]interface{}, len(functionMap)+2)
95+
for k, v := range functionMap {
96+
functions[k] = v
97+
}
98+
functions[functionLineName] = func() string {
99+
return currLine()
100+
}
101+
functions[functionTimestampName] = func() time.Time {
102+
return time.Unix(0, currTimestamp())
103+
}
104+
return functions
105+
}
106+
93107
func init() {
94108
sprigFuncMap := sprig.GenericFuncMap()
95109
for _, v := range templateFunctions {
@@ -112,16 +126,13 @@ func NewFormatter(tmpl string) (*LineFormatter, error) {
112126
lf := &LineFormatter{
113127
buf: bytes.NewBuffer(make([]byte, 4096)),
114128
}
115-
functions := make(map[string]interface{}, len(functionMap)+1)
116-
for k, v := range functionMap {
117-
functions[k] = v
118-
}
119-
functions[functionLineName] = func() string {
129+
130+
functions := addLineAndTimestampFunctions(func() string {
120131
return unsafeGetString(lf.currentLine)
121-
}
122-
functions[functionTimestampName] = func() time.Time {
123-
return time.Unix(0, lf.currentTs)
124-
}
132+
}, func() int64 {
133+
return lf.currentTs
134+
})
135+
125136
t, err := template.New("line").Option("missingkey=zero").Funcs(functions).Parse(tmpl)
126137
if err != nil {
127138
return nil, fmt.Errorf("invalid line template: %w", err)
@@ -235,6 +246,9 @@ type labelFormatter struct {
235246
type LabelsFormatter struct {
236247
formats []labelFormatter
237248
buf *bytes.Buffer
249+
250+
currentLine []byte
251+
currentTs int64
238252
}
239253

240254
// NewLabelsFormatter creates a new formatter that can format multiple labels at once.
@@ -246,21 +260,29 @@ func NewLabelsFormatter(fmts []LabelFmt) (*LabelsFormatter, error) {
246260
}
247261
formats := make([]labelFormatter, 0, len(fmts))
248262

263+
lf := &LabelsFormatter{
264+
buf: bytes.NewBuffer(make([]byte, 1024)),
265+
}
266+
267+
functions := addLineAndTimestampFunctions(func() string {
268+
return unsafeGetString(lf.currentLine)
269+
}, func() int64 {
270+
return lf.currentTs
271+
})
272+
249273
for _, fm := range fmts {
250274
toAdd := labelFormatter{LabelFmt: fm}
251275
if !fm.Rename {
252-
t, err := template.New("label").Option("missingkey=zero").Funcs(functionMap).Parse(fm.Value)
276+
t, err := template.New("label").Option("missingkey=zero").Funcs(functions).Parse(fm.Value)
253277
if err != nil {
254278
return nil, fmt.Errorf("invalid template for label '%s': %s", fm.Name, err)
255279
}
256280
toAdd.tmpl = t
257281
}
258282
formats = append(formats, toAdd)
259283
}
260-
return &LabelsFormatter{
261-
formats: formats,
262-
buf: bytes.NewBuffer(make([]byte, 1024)),
263-
}, nil
284+
lf.formats = formats
285+
return lf, nil
264286
}
265287

266288
func validate(fmts []LabelFmt) error {
@@ -279,7 +301,10 @@ func validate(fmts []LabelFmt) error {
279301
return nil
280302
}
281303

282-
func (lf *LabelsFormatter) Process(_ int64, l []byte, lbs *LabelsBuilder) ([]byte, bool) {
304+
func (lf *LabelsFormatter) Process(ts int64, l []byte, lbs *LabelsBuilder) ([]byte, bool) {
305+
lf.currentLine = l
306+
lf.currentTs = ts
307+
283308
var data interface{}
284309
for _, f := range lf.formats {
285310
if f.Rename {

pkg/logql/log/fmt_test.go

+31-1
Original file line numberDiff line numberDiff line change
@@ -420,13 +420,43 @@ func Test_labelsFormatter_Format(t *testing.T) {
420420
{Name: "__error_details__", Value: "template: label:1:2: executing \"label\" at <replace>: wrong number of args for replace: want 3 got 2"},
421421
},
422422
},
423+
{
424+
"line",
425+
mustNewLabelsFormatter([]LabelFmt{NewTemplateLabelFmt("line", "{{ __line__ }}")}),
426+
labels.Labels{{Name: "foo", Value: "blip"}, {Name: "bar", Value: "blop"}},
427+
labels.Labels{
428+
{Name: "foo", Value: "blip"},
429+
{Name: "bar", Value: "blop"},
430+
{Name: "line", Value: "test line"},
431+
},
432+
},
433+
{
434+
"timestamp",
435+
mustNewLabelsFormatter([]LabelFmt{NewTemplateLabelFmt("ts", "{{ __timestamp__ | date \"2006-01-02\" }}")}),
436+
labels.Labels{{Name: "foo", Value: "blip"}, {Name: "bar", Value: "blop"}},
437+
labels.Labels{
438+
{Name: "foo", Value: "blip"},
439+
{Name: "bar", Value: "blop"},
440+
{Name: "ts", Value: "2022-08-26"},
441+
},
442+
},
443+
{
444+
"timestamp_unix",
445+
mustNewLabelsFormatter([]LabelFmt{NewTemplateLabelFmt("ts", "{{ __timestamp__ | unixEpoch }}")}),
446+
labels.Labels{{Name: "foo", Value: "blip"}, {Name: "bar", Value: "blop"}},
447+
labels.Labels{
448+
{Name: "foo", Value: "blip"},
449+
{Name: "bar", Value: "blop"},
450+
{Name: "ts", Value: "1661518453"},
451+
},
452+
},
423453
}
424454

425455
for _, tt := range tests {
426456
t.Run(tt.name, func(t *testing.T) {
427457
builder := NewBaseLabelsBuilder().ForLabels(tt.in, tt.in.Hash())
428458
builder.Reset()
429-
_, _ = tt.fmter.Process(0, nil, builder)
459+
_, _ = tt.fmter.Process(1661518453244672570, []byte("test line"), builder)
430460
sort.Sort(tt.want)
431461
require.Equal(t, tt.want, builder.LabelsResult().Labels())
432462
})

0 commit comments

Comments
 (0)