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

Issue:
azuremonitorexporter flushes on each first Span
### Component(s)

exporter/azuremonitor

### What happened?

## Description
AzureMonitorExporter calls Flush on each Span.
It is caused by [this](https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/36520) commit. It seems that Flush must be called after all Spans collected into batches, outside the Resourse/ Scope/ Spans loop.

## Steps to Reproduce
To have constant Spans flow into OTEL Collector with AzureMonitorExporter enabled.

## Expected Result
AzureMonitorExporter collects Spans in batches and calls Flush.

## Actual Result
AzureMonitorExporter calls Flush on each Span.

### Collector version

v0.116.0



Discussion:
Pinging code owners:
- exporter/azuremonitor: @pcwiese

 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.




Patch:
diff --git a/.chloggen/fix_azure-monitor-exporter-flush-ddos.yaml b/.chloggen/fix_azure-monitor-exporter-flush-ddos.yaml
new file mode 100644
index 0000000000000..1390e5d197226
--- /dev/null
+++ b/.chloggen/fix_azure-monitor-exporter-flush-ddos.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: azuremonitorexporter
+
+# A brief description of the change.  Surround your text with quotes ("") if it needs to start with a backtick (`).
+note: Fix flushes on each single Span
+
+# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
+issues: [37214]
+
+# (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: [user]
diff --git a/exporter/azuremonitorexporter/azuremonitor_exporter.go b/exporter/azuremonitorexporter/azuremonitor_exporter.go
index c7376a45e55fe..34dcc29a90a74 100644
--- a/exporter/azuremonitorexporter/azuremonitor_exporter.go
+++ b/exporter/azuremonitorexporter/azuremonitor_exporter.go
@@ -122,8 +122,6 @@ func (v *traceVisitor) visit(
 		v.exporter.transportChannel.Send(envelope)
 	}
 
-	// Flush the transport channel to force the telemetry to be sent
-	v.exporter.transportChannel.Flush()
 	v.processed++
 
 	return true
@@ -137,5 +135,11 @@ func (exporter *azureMonitorExporter) consumeTraces(_ context.Context, traceData
 
 	visitor := &traceVisitor{exporter: exporter}
 	accept(traceData, visitor)
+
+	// Flush the transport channel to force the telemetry to be sent
+	if visitor.processed > 0 {
+		exporter.transportChannel.Flush()
+	}
+
 	return visitor.err
 }


Test patch:
diff --git a/exporter/azuremonitorexporter/traceexporter_test.go b/exporter/azuremonitorexporter/traceexporter_test.go
index 1c2804584ff40..aa420fd5b7416 100644
--- a/exporter/azuremonitorexporter/traceexporter_test.go
+++ b/exporter/azuremonitorexporter/traceexporter_test.go
@@ -28,6 +28,7 @@ func TestExporterTraceDataCallbackNoSpans(t *testing.T) {
 	assert.NoError(t, exporter.consumeTraces(context.Background(), traces))
 
 	mockTransportChannel.AssertNumberOfCalls(t, "Send", 0)
+	mockTransportChannel.AssertNumberOfCalls(t, "Flush", 0)
 }
 
 // Tests the export onTraceData callback with a single Span
@@ -51,6 +52,34 @@ func TestExporterTraceDataCallbackSingleSpan(t *testing.T) {
 	assert.NoError(t, exporter.consumeTraces(context.Background(), traces))
 
 	mockTransportChannel.AssertNumberOfCalls(t, "Send", 1)
+	mockTransportChannel.AssertNumberOfCalls(t, "Flush", 1)
+}
+
+// Tests the export onTraceData callback calls exporter flush only once for 8 spans
+func TestExporterTraceDataCallbackCallFlushOnce(t *testing.T) {
+	mockTransportChannel := getMockTransportChannel()
+	exporter := getExporter(defaultConfig, mockTransportChannel)
+
+	resource := getResource()
+	scope := getScope()
+	span := getDefaultHTTPServerSpan()
+
+	traces := ptrace.NewTraces()
+	rs := traces.ResourceSpans().AppendEmpty()
+	r := rs.Resource()
+	resource.CopyTo(r)
+	ilss := rs.ScopeSpans().AppendEmpty()
+	scope.CopyTo(ilss.Scope())
+
+	span.CopyTo(ilss.Spans().AppendEmpty())
+	span.CopyTo(ilss.Spans().AppendEmpty())
+	ilss.CopyTo(rs.ScopeSpans().AppendEmpty())
+	rs.CopyTo(traces.ResourceSpans().AppendEmpty())
+
+	assert.NoError(t, exporter.consumeTraces(context.Background(), traces))
+
+	mockTransportChannel.AssertNumberOfCalls(t, "Send", 8)
+	mockTransportChannel.AssertNumberOfCalls(t, "Flush", 1)
 }
 
 // Tests the export onTraceData callback with a single Span with SpanEvents
@@ -83,6 +112,7 @@ func TestExporterTraceDataCallbackSingleSpanWithSpanEvents(t *testing.T) {
 	assert.NoError(t, exporter.consumeTraces(context.Background(), traces))
 
 	mockTransportChannel.AssertNumberOfCalls(t, "Send", 3)
+	mockTransportChannel.AssertNumberOfCalls(t, "Flush", 1)
 }
 
 // Tests the export onTraceData callback with a single Span that fails to produce an envelope
@@ -112,6 +142,7 @@ func TestExporterTraceDataCallbackSingleSpanNoEnvelope(t *testing.T) {
 	assert.True(t, consumererror.IsPermanent(err), "error should be permanent")
 
 	mockTransportChannel.AssertNumberOfCalls(t, "Send", 0)
+	mockTransportChannel.AssertNumberOfCalls(t, "Flush", 0)
 }
 
 func getMockTransportChannel() *mockTransportChannel {