Repository: open-telemetry/opentelemetry-collector-contrib (Go)

Issue:
invalid memory address or nil pointer dereference with log files larger than 1 MB
### Component(s)

receiver/otlpjsonfile

### What happened?

## Description
When the log file size being read exceeds 1 MB, collector errors out with invalid memory address or nil pointer dereference. The stack trace is 
```
2025-03-02T17:07:03.725-0600    info    fileconsumer/file.go:265        Started watching file   {"otelcol.component.id": "otlpjsonfile", "otelcol.component.kind": "Receiver", "otelcol.signal": "traces", "component": "fileconsumer", "path": "C:\\otel-file-export\\logs-metrics.log"}
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x8 pc=0x71d8005]

goroutine 29 [running]:
go.opentelemetry.io/collector/pdata/plog.Logs.ResourceLogs(...)
        go.opentelemetry.io/collector/pdata@v1.26.0/plog/logs.go:60
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otlpjsonfilereceiver.createLogsReceiver.func1({0xc1662f0?, 0xc0011d2690?}, {{0xc002040000?, 0x100000?, 0x0?}, 0xc0014065a0?})
        github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otlpjsonfilereceiver@v0.120.1/file.go:92 +0x145
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/fileconsumer/internal/reader.(*Reader).readContents(0xc00197c270, {0xc1662f0, 0xc0011d2690})
        github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza@v0.120.1/fileconsumer/internal/reader/reader.go:221 +0x303
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/fileconsumer/internal/reader.(*Reader).ReadToEnd(0xc00197c270, {0xc1662f0, 0xc0011d2690})
        github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza@v0.120.1/fileconsumer/internal/reader/reader.go:117 +0x7c8
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/fileconsumer.(*Manager).consume.func1(0xc00197c270)
        github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza@v0.120.1/fileconsumer/file.go:169 +0x9e
created by github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/fileconsumer.(*Manager).consume in goroutine 86
        github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza@v0.120.1/fileconsumer/file.go:166 +0x1ea
```
## Steps to Reproduce
Configure pipeline to export logs to disk using file exporter. Default settings with max_megabytes set to 2.
Try to read the logs using a separate pipeline with otlpjsonfile receiver. Exporting to debug console for testing.

## Expected Result
Log files should be parsed and should be written to console

## Actual Result
Log files are parsed if size is small ( less than 1 MB). For any higher size, nil pointer exception is thrown


### Collector version

0.120.1

### Environment information

## Environment
OS: Windows 11



### OpenTelemetry Collector configuration

```yaml
receivers:
  otlpjsonfile:
    include:
      - "C:/otel-file-export/*.log"

processors:
  batch:

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    metrics:
      receivers: [otlpjsonfile]
      processors: [batch]
      exporters: [debug]
    traces:
      receivers: [otlpjsonfile]
      processors: [batch]
      exporters: [debug]
    logs:
      receivers: [otlpjsonfile]
      processors: [batch]
      exporters: [debug]
```

### Log output

```shell

```

### Additional context

_No response_


Discussion:
Pinging code owners:
- receiver/otlpjsonfile: @atoulme

 See [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#adding-labels-via-comments) if you do not have permissions to add labels yourself.

Pinging code owners for pkg/stanza/fileconsumer: @djaglowski. See [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#adding-labels-via-comments) if you do not have permissions to add labels yourself. For example, comment '/label priority:p2 -needs-triaged' to set the priority and remove the needs-triaged label.
@atoulme I'm marking this as a release blocker as it's a regression due to https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/36715.
Fix: https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/38354
@pantanuj can you please confirm the fix by taking the latest pull?
I don't have ability to currently compile the code but I was able to pull in the built binary from the pipeline and test it.

![Image](https://github.com/user-attachments/assets/38764dcf-6ed5-40d7-be0c-9b1d363ff627)

 Do not get the error anymore but for larger files it does not show any signals.

![Image](https://github.com/user-attachments/assets/392b7519-80ed-437c-8660-e876fdc886ad)





Patch:
diff --git a/.chloggen/fix-otlpjsonfilereceiver-null-dereference.yaml b/.chloggen/fix-otlpjsonfilereceiver-null-dereference.yaml
new file mode 100644
index 0000000000000..a1f03a09c95c4
--- /dev/null
+++ b/.chloggen/fix-otlpjsonfilereceiver-null-dereference.yaml
@@ -0,0 +1,27 @@
+# Use this changelog template to create an entry for release notes.
+
+# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
+change_type: bug_fix
+
+# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
+component: otlpjsonfilereceiver
+
+# A brief description of the change.  Surround your text with quotes ("") if it needs to start with a backtick (`).
+note: Fix nil pointer dereference due to empty token
+
+# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
+issues: [38289]
+
+# (Optional) One or more lines of additional information to render under the primary note.
+# These lines will be padded with 2 spaces and then inserted directly into the document.
+# Use pipe (|) for multiline entries.
+subtext:
+
+# If your change doesn't affect end users or the exported elements of any package,
+# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
+# Optional: The change log or logs in which this entry should be included.
+# e.g. '[user]' or '[user, api]'
+# Include 'user' if the change is relevant to end users.
+# Include 'api' if there is a change to a library API.
+# Default: '[user]'
+change_logs: []
diff --git a/receiver/otlpjsonfilereceiver/file.go b/receiver/otlpjsonfilereceiver/file.go
index e5ee0614057b9..8ec96bfc6d67e 100644
--- a/receiver/otlpjsonfilereceiver/file.go
+++ b/receiver/otlpjsonfilereceiver/file.go
@@ -88,22 +88,22 @@ func createLogsReceiver(_ context.Context, settings receiver.Settings, configura
 			ctx = obsrecv.StartLogsOp(ctx)
 			var l plog.Logs
 			l, err = logsUnmarshaler.UnmarshalLogs(token)
-			// Appends token.Attributes
-			for i := 0; i < l.ResourceLogs().Len(); i++ {
-				resourceLog := l.ResourceLogs().At(i)
-				for j := 0; j < resourceLog.ScopeLogs().Len(); j++ {
-					scopeLog := resourceLog.ScopeLogs().At(j)
-					for k := 0; k < scopeLog.LogRecords().Len(); k++ {
-						LogRecords := scopeLog.LogRecords().At(k)
-						appendToMap(attributes, LogRecords.Attributes())
-					}
-				}
-			}
 			if err != nil {
 				obsrecv.EndLogsOp(ctx, metadata.Type.String(), 0, err)
 			} else {
 				logRecordCount := l.LogRecordCount()
 				if logRecordCount != 0 {
+					// Appends token.Attributes
+					for i := 0; i < l.ResourceLogs().Len(); i++ {
+						resourceLog := l.ResourceLogs().At(i)
+						for j := 0; j < resourceLog.ScopeLogs().Len(); j++ {
+							scopeLog := resourceLog.ScopeLogs().At(j)
+							for k := 0; k < scopeLog.LogRecords().Len(); k++ {
+								LogRecords := scopeLog.LogRecords().At(k)
+								appendToMap(attributes, LogRecords.Attributes())
+							}
+						}
+					}
 					err = logs.ConsumeLogs(ctx, l)
 				}
 				obsrecv.EndLogsOp(ctx, metadata.Type.String(), logRecordCount, err)
@@ -138,21 +138,21 @@ func createMetricsReceiver(_ context.Context, settings receiver.Settings, config
 			ctx = obsrecv.StartMetricsOp(ctx)
 			var m pmetric.Metrics
 			m, err = metricsUnmarshaler.UnmarshalMetrics(token)
-			// Appends token.Attributes
-			for i := 0; i < m.ResourceMetrics().Len(); i++ {
-				resourceMetric := m.ResourceMetrics().At(i)
-				for j := 0; j < resourceMetric.ScopeMetrics().Len(); j++ {
-					ScopeMetric := resourceMetric.ScopeMetrics().At(j)
-					for k := 0; k < ScopeMetric.Metrics().Len(); k++ {
-						metric := ScopeMetric.Metrics().At(k)
-						appendToMap(attributes, metric.Metadata())
-					}
-				}
-			}
 			if err != nil {
 				obsrecv.EndMetricsOp(ctx, metadata.Type.String(), 0, err)
 			} else {
 				if m.ResourceMetrics().Len() != 0 {
+					// Appends token.Attributes
+					for i := 0; i < m.ResourceMetrics().Len(); i++ {
+						resourceMetric := m.ResourceMetrics().At(i)
+						for j := 0; j < resourceMetric.ScopeMetrics().Len(); j++ {
+							ScopeMetric := resourceMetric.ScopeMetrics().At(j)
+							for k := 0; k < ScopeMetric.Metrics().Len(); k++ {
+								metric := ScopeMetric.Metrics().At(k)
+								appendToMap(attributes, metric.Metadata())
+							}
+						}
+					}
 					err = metrics.ConsumeMetrics(ctx, m)
 				}
 				obsrecv.EndMetricsOp(ctx, metadata.Type.String(), m.MetricCount(), err)
@@ -187,21 +187,21 @@ func createTracesReceiver(_ context.Context, settings receiver.Settings, configu
 			ctx = obsrecv.StartTracesOp(ctx)
 			var t ptrace.Traces
 			t, err = tracesUnmarshaler.UnmarshalTraces(token)
-			// Appends token.Attributes
-			for i := 0; i < t.ResourceSpans().Len(); i++ {
-				resourceSpan := t.ResourceSpans().At(i)
-				for j := 0; j < resourceSpan.ScopeSpans().Len(); j++ {
-					scopeSpan := resourceSpan.ScopeSpans().At(j)
-					for k := 0; k < scopeSpan.Spans().Len(); k++ {
-						spans := scopeSpan.Spans().At(k)
-						appendToMap(attributes, spans.Attributes())
-					}
-				}
-			}
 			if err != nil {
 				obsrecv.EndTracesOp(ctx, metadata.Type.String(), 0, err)
 			} else {
 				if t.ResourceSpans().Len() != 0 {
+					// Appends token.Attributes
+					for i := 0; i < t.ResourceSpans().Len(); i++ {
+						resourceSpan := t.ResourceSpans().At(i)
+						for j := 0; j < resourceSpan.ScopeSpans().Len(); j++ {
+							scopeSpan := resourceSpan.ScopeSpans().At(j)
+							for k := 0; k < scopeSpan.Spans().Len(); k++ {
+								spans := scopeSpan.Spans().At(k)
+								appendToMap(attributes, spans.Attributes())
+							}
+						}
+					}
 					err = traces.ConsumeTraces(ctx, t)
 				}
 				obsrecv.EndTracesOp(ctx, metadata.Type.String(), t.SpanCount(), err)


Test patch:
diff --git a/receiver/otlpjsonfilereceiver/file_test.go b/receiver/otlpjsonfilereceiver/file_test.go
index b0d0339c97ef6..8446a89f8e434 100644
--- a/receiver/otlpjsonfilereceiver/file_test.go
+++ b/receiver/otlpjsonfilereceiver/file_test.go
@@ -304,3 +304,56 @@ func TestFileMixedSignals(t *testing.T) {
 	err = pr.Shutdown(context.Background())
 	assert.NoError(t, err)
 }
+
+func TestEmptyLine(t *testing.T) {
+	tempFolder := t.TempDir()
+	factory := NewFactory()
+	cfg := createDefaultConfig().(*Config)
+	cfg.Config.Include = []string{filepath.Join(tempFolder, "*")}
+	cfg.Config.StartAt = "beginning"
+	cs := receivertest.NewNopSettings(metadata.Type)
+	t.Run("metrics receiver", func(t *testing.T) {
+		ms := new(consumertest.MetricsSink)
+		mr, err := factory.CreateMetrics(context.Background(), cs, cfg, ms)
+		assert.NoError(t, err)
+		err = mr.Start(context.Background(), nil)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, mr.Shutdown(context.Background()))
+		}()
+		err = os.WriteFile(filepath.Join(tempFolder, "metrics.json"), []byte{'\n', '\n'}, 0o600)
+		assert.NoError(t, err)
+		time.Sleep(1 * time.Second)
+		require.Empty(t, ms.AllMetrics())
+	})
+
+	t.Run("trace receiver", func(t *testing.T) {
+		ts := new(consumertest.TracesSink)
+		tr, err := factory.CreateTraces(context.Background(), cs, cfg, ts)
+		assert.NoError(t, err)
+		err = tr.Start(context.Background(), nil)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, tr.Shutdown(context.Background()))
+		}()
+		err = os.WriteFile(filepath.Join(tempFolder, "traces.json"), []byte{'\n', '\n'}, 0o600)
+		assert.NoError(t, err)
+		time.Sleep(1 * time.Second)
+		require.Empty(t, ts.AllTraces())
+	})
+
+	t.Run("log receiver", func(t *testing.T) {
+		ls := new(consumertest.LogsSink)
+		lr, err := factory.CreateLogs(context.Background(), cs, cfg, ls)
+		assert.NoError(t, err)
+		err = lr.Start(context.Background(), nil)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, lr.Shutdown(context.Background()))
+		}()
+		err = os.WriteFile(filepath.Join(tempFolder, "logs.json"), []byte{'\n', '\n'}, 0o600)
+		assert.NoError(t, err)
+		time.Sleep(1 * time.Second)
+		require.Empty(t, ls.AllLogs())
+	})
+}