From 291140b0d85c54c17a5af0d0b98772b141139ebd Mon Sep 17 00:00:00 2001 From: arjun <119788439+arjun7579@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:20:43 +0530 Subject: [PATCH] log: Add Record.Clone (#7001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #6986 This PR adds a `Clone()` method to the `log.Record` type. The `Clone` method returns a copy of the record with no shared state, allowing both the original and the clone to be modified independently. This functionality mirrors the existing `sdk/log.Record.Clone()` behavior and includes a corresponding unit test (`TestRecordClone`) to ensure correctness. --------- Co-authored-by: Robert PajÄ…k --- CHANGELOG.md | 1 + log/record.go | 8 ++++++ log/record_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64d910962..80054532e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `RPCGRPCResponseMetadata` - Add `ErrorType` attribute helper function to the `go.opentelmetry.io/otel/semconv/v1.34.0` package. (#6962) - Add `WithAllowKeyDuplication` in `go.opentelemetry.io/otel/sdk/log` which can be used to disable deduplication for log records. (#6968) +- Add `Clone` method to `Record` in `go.opentelemetry.io/otel/log` that returns a copy of the record with no shared state. (#7001) ### Changed diff --git a/log/record.go b/log/record.go index 4d2f32d0f..adde7a0dc 100644 --- a/log/record.go +++ b/log/record.go @@ -142,3 +142,11 @@ func (r *Record) AddAttributes(attrs ...KeyValue) { func (r *Record) AttributesLen() int { return r.nFront + len(r.back) } + +// Clone returns a copy of the record with no shared state. +// The original record and the clone can both be modified without interfering with each other. +func (r *Record) Clone() Record { + res := *r + res.back = slices.Clone(r.back) + return res +} diff --git a/log/record_test.go b/log/record_test.go index b3f145fe0..7e44bbfc0 100644 --- a/log/record_test.go +++ b/log/record_test.go @@ -160,3 +160,64 @@ func TestRecordAllocationLimits(t *testing.T) { // Convince the linter these values are used. _, _, _, _, _, _ = tStamp, sev, text, body, n, attr } + +func TestRecordClone(t *testing.T) { + now0 := time.Now() + sev0 := log.SeverityInfo + text0 := "text" + val0 := log.BoolValue(true) + attr0 := log.Bool("0", true) + + r0 := log.Record{} + r0.SetTimestamp(now0) + r0.SetObservedTimestamp(now0) + r0.SetSeverity(sev0) + r0.SetSeverityText(text0) + r0.SetBody(val0) + r0.AddAttributes(attr0) + + // Clone and modify the clone + now1 := now0.Add(time.Second) + sev1 := log.SeverityDebug + text1 := "string" + val1 := log.IntValue(1) + attr1 := log.Int64("1", 2) + + r1 := r0.Clone() + r1.SetTimestamp(now1) + r1.SetObservedTimestamp(now1) + r1.SetSeverity(sev1) + r1.SetSeverityText(text1) + r1.SetBody(val1) + r1.AddAttributes(attr1) + + // Assertions on original record (r0) + assert.Equal(t, now0, r0.Timestamp()) + assert.Equal(t, now0, r0.ObservedTimestamp()) + assert.Equal(t, sev0, r0.Severity()) + assert.Equal(t, text0, r0.SeverityText()) + assert.True(t, val0.Equal(r0.Body())) + + var r0Attrs []log.KeyValue + r0.WalkAttributes(func(kv log.KeyValue) bool { + r0Attrs = append(r0Attrs, kv) + return true + }) + assert.Contains(t, r0Attrs, attr0) + assert.NotContains(t, r0Attrs, attr1) + + // Assertions on cloned record (r1) + assert.Equal(t, now1, r1.Timestamp()) + assert.Equal(t, now1, r1.ObservedTimestamp()) + assert.Equal(t, sev1, r1.Severity()) + assert.Equal(t, text1, r1.SeverityText()) + assert.True(t, val1.Equal(r1.Body())) + + var r1Attrs []log.KeyValue + r1.WalkAttributes(func(kv log.KeyValue) bool { + r1Attrs = append(r1Attrs, kv) + return true + }) + assert.Contains(t, r1Attrs, attr0) + assert.Contains(t, r1Attrs, attr1) +}