Skip to content

Commit 853205c

Browse files
committed
feat: add parameter to allow to log SSE trafic
Allow APIs to log setup custom Content-Type filter https://gravitee.atlassian.net/browse/APIM-11603
1 parent 8e3ee00 commit 853205c

File tree

13 files changed

+105
-85
lines changed

13 files changed

+105
-85
lines changed

gravitee-apim-console-webui/src/entities/management-api-v2/api/v4/loggingV4.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ export interface LoggingV4 {
2323
content?: LoggingContentV4;
2424
phase?: LoggingPhase;
2525
mode?: LoggingModeV4;
26+
overrideContentTypeValidation?: string;
2627
}

gravitee-apim-console-webui/src/management/api/reporter-settings/reporter-settings-proxy/reporter-settings-proxy.component.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@
8787
<input matInput formControlName="condition" />
8888
<mat-hint> Support EL (e.g: &#123; #request.headers['Content-Type'][0] == 'application/json' &#125;)</mat-hint>
8989
</mat-form-field>
90+
91+
<gio-banner-warning>
92+
<div>Important Note</div>
93+
<span gioBannerBody
94+
>To ensure stable operation, avoid logging oversized or binary data. Default value is:<br />
95+
<code>video.*|audio.*|image.*|application/octet-stream|application/pdf|text/event-stream</code></span
96+
>
97+
</gio-banner-warning>
98+
<mat-form-field>
99+
<mat-label>Content-Type filtering</mat-label>
100+
<input matInput formControlName="overrideContentTypeValidation" />
101+
<mat-hint>Regular expression to filter out queries by Content-Type; matching payloads won’t be logged.</mat-hint>
102+
</mat-form-field>
90103
</div>
91104

92105
<div class="settings__form-control">

gravitee-apim-console-webui/src/management/api/reporter-settings/reporter-settings-proxy/reporter-settings-proxy.component.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ describe('ApiRuntimeLogsProxySettingsComponent', () => {
112112
await componentHarness.toggleHeaders();
113113
await componentHarness.togglePayload();
114114
await componentHarness.setCondition('condition');
115+
await componentHarness.setOverrideContentTypeValidation('video/.*|image/.*');
115116

116117
await componentHarness.clickOnSaveButton();
117118
expectApiGetRequest(api);
@@ -121,6 +122,7 @@ describe('ApiRuntimeLogsProxySettingsComponent', () => {
121122
...api.analytics,
122123
logging: {
123124
mode: { entrypoint: true, endpoint: true },
125+
overrideContentTypeValidation: 'video/.*|image/.*',
124126
phase: { request: true, response: true },
125127
content: { headers: true, payload: true },
126128
condition: 'condition',
@@ -140,6 +142,7 @@ describe('ApiRuntimeLogsProxySettingsComponent', () => {
140142
phase: { request: true, response: true },
141143
content: { headers: true, payload: true },
142144
condition: 'condition',
145+
overrideContentTypeValidation: 'video/.*|image/.*',
143146
},
144147
},
145148
});
@@ -152,6 +155,7 @@ describe('ApiRuntimeLogsProxySettingsComponent', () => {
152155
expect(await componentHarness.isHeadersChecked()).toStrictEqual(true);
153156
expect(await componentHarness.isPayloadChecked()).toStrictEqual(true);
154157
expect(await componentHarness.getCondition()).toStrictEqual('condition');
158+
expect(await componentHarness.getOverrideContentTypeValidation()).toStrictEqual('video/.*|image/.*');
155159
});
156160

157161
it('should discard the changes', async () => {
@@ -162,6 +166,7 @@ describe('ApiRuntimeLogsProxySettingsComponent', () => {
162166
await componentHarness.toggleHeaders();
163167
await componentHarness.togglePayload();
164168
await componentHarness.setCondition('condition');
169+
await componentHarness.setOverrideContentTypeValidation('video/.*|image/.*');
165170

166171
expect(await componentHarness.isEntrypointChecked()).toStrictEqual(true);
167172
expect(await componentHarness.isEndpointChecked()).toStrictEqual(true);
@@ -170,6 +175,7 @@ describe('ApiRuntimeLogsProxySettingsComponent', () => {
170175
expect(await componentHarness.isHeadersChecked()).toStrictEqual(true);
171176
expect(await componentHarness.isPayloadChecked()).toStrictEqual(true);
172177
expect(await componentHarness.getCondition()).toStrictEqual('condition');
178+
expect(await componentHarness.getOverrideContentTypeValidation()).toStrictEqual('video/.*|image/.*');
173179
await checkLoggingFieldsEnabled();
174180

175181
await componentHarness.clickOnResetButton();
@@ -183,6 +189,7 @@ describe('ApiRuntimeLogsProxySettingsComponent', () => {
183189
expect(await componentHarness.isHeadersChecked()).toStrictEqual(false);
184190
expect(await componentHarness.isPayloadChecked()).toStrictEqual(false);
185191
expect(await componentHarness.getCondition()).toStrictEqual('');
192+
expect(await componentHarness.getOverrideContentTypeValidation()).toStrictEqual('');
186193
await checkLoggingFieldsDisabled();
187194
});
188195
});
@@ -263,6 +270,7 @@ describe('ApiRuntimeLogsProxySettingsComponent', () => {
263270
enabled: true,
264271
logging: {
265272
mode: { entrypoint: true, endpoint: true },
273+
overrideContentTypeValidation: undefined,
266274
phase: { request: true, response: true },
267275
content: { headers: true, payload: true },
268276
condition: 'condition',

gravitee-apim-console-webui/src/management/api/reporter-settings/reporter-settings-proxy/reporter-settings-proxy.component.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
import { Component, DestroyRef, inject, input, InputSignal, OnInit } from '@angular/core';
17-
import {
18-
FormControl,
19-
FormGroup,
20-
FormsModule,
21-
ReactiveFormsModule
22-
} from "@angular/forms";
17+
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
2318
import { EMPTY, merge } from 'rxjs';
2419
import { catchError, switchMap, tap } from 'rxjs/operators';
2520
import { ActivatedRoute } from '@angular/router';
@@ -47,14 +42,12 @@ const BOOLEAN_CONFIG_KEYS = [
4742
'headers',
4843
'payload',
4944
'tracingEnabled',
50-
'tracingVerbose'
45+
'tracingVerbose',
5146
] as const;
5247

5348
type BooleanConfig = (typeof BOOLEAN_CONFIG_KEYS)[number];
5449

55-
const STRING_CONFIG_KEYS = [
56-
'condition'
57-
] as const;
50+
const STRING_CONFIG_KEYS = ['condition', 'overrideContentTypeValidation'] as const;
5851
type StringConfig = (typeof STRING_CONFIG_KEYS)[number];
5952

6053
type DefaultConfiguration = Record<BooleanConfig, boolean> & Record<StringConfig, string>;
@@ -109,6 +102,7 @@ export class ReporterSettingsProxyComponent implements OnInit {
109102
...api.analytics,
110103
enabled: configurationValues.enabled,
111104
logging: {
105+
overrideContentTypeValidation: configurationValues.overrideContentTypeValidation,
112106
condition: configurationValues.condition,
113107
mode: {
114108
entrypoint: configurationValues.entrypoint,
@@ -185,6 +179,10 @@ export class ReporterSettingsProxyComponent implements OnInit {
185179
value: api.analytics?.logging?.condition,
186180
disabled: !analyticsEnabled || !atLeastModeIsEnabled || isReadOnly,
187181
}),
182+
overrideContentTypeValidation: new FormControl({
183+
value: api.analytics?.logging?.overrideContentTypeValidation,
184+
disabled: !analyticsEnabled || !atLeastModeIsEnabled || isReadOnly,
185+
}),
188186
});
189187

190188
this.defaultConfiguration = this.form.getRawValue();
@@ -260,7 +258,7 @@ export class ReporterSettingsProxyComponent implements OnInit {
260258
}
261259

262260
private clearAndDisableLoggingFormFields() {
263-
BOOLEAN_CONFIG_KEYS.forEach((key) => {
261+
['request', 'response', 'headers', 'payload'].forEach((key) => {
264262
this.form.get(key).setValue(false);
265263
this.form.get(key).disable();
266264
});

gravitee-apim-console-webui/src/management/api/reporter-settings/reporter-settings-proxy/reporter-settings-proxy.harness.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class ReporterSettingsProxyHarness extends ComponentHarness {
3131
private getTracingEnabledToggle = this.locatorFor(MatSlideToggleHarness.with({ selector: '[formControlName="tracingEnabled"]' }));
3232
private getTracingVerboseToggle = this.locatorFor(MatSlideToggleHarness.with({ selector: '[formControlName="tracingVerbose"]' }));
3333
private getConditionInput = this.locatorFor(MatInputHarness.with({ selector: '[formControlName="condition"]' }));
34+
private getOverrideContentTypeValidationInput = this.locatorFor(MatInputHarness.with({ selector: '[formControlName="overrideContentTypeValidation"]' }));
3435
private getSaveBar = this.locatorFor(GioSaveBarHarness);
3536

3637
public isEnabledChecked = async () => (await this.getEnabledToggle()).isChecked();
@@ -56,6 +57,9 @@ export class ReporterSettingsProxyHarness extends ComponentHarness {
5657
public getCondition = async () => (await this.getConditionInput()).getValue();
5758
public isConditionDisabled = async () => (await this.getConditionInput()).isDisabled();
5859
public setCondition = async (condition: string) => (await this.getConditionInput()).setValue(condition);
60+
public getOverrideContentTypeValidation = async () => (await this.getOverrideContentTypeValidationInput()).getValue();
61+
public isOverrideContentTypeValidationDisabled = async () => (await this.getOverrideContentTypeValidationInput()).isDisabled();
62+
public setOverrideContentTypeValidation = async (validation: string) => (await this.getOverrideContentTypeValidationInput()).setValue(validation);
5963
public clickOnSaveButton = async () => (await this.getSaveBar()).clickSubmit();
6064
public clickOnResetButton = async () => (await this.getSaveBar()).clickReset();
6165

gravitee-apim-definition/gravitee-apim-definition-model/src/main/java/io/gravitee/definition/model/v4/analytics/logging/Logging.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,6 @@ public class Logging implements Serializable {
5151
private String condition;
5252

5353
private String messageCondition;
54+
55+
private String overrideContentTypeValidation;
5456
}

gravitee-apim-gateway/gravitee-apim-gateway-core/src/main/java/io/gravitee/gateway/core/logging/utils/LoggingUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
public final class LoggingUtils {
3636

3737
private static final String DEFAULT_EXCLUDED_CONTENT_TYPES =
38-
"video.*|audio.*|image.*|application\\/octet-stream|application\\/pdf|text\\/event-stream";
38+
"video.*|audio.*|image.*|application/octet-stream|application/pdf|text/event-stream";
3939

4040
private static Pattern EXCLUDED_CONTENT_TYPES_PATTERN;
4141

gravitee-apim-gateway/gravitee-apim-gateway-core/src/main/java/io/gravitee/gateway/reactive/core/v4/analytics/LoggingContext.java

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020
import io.gravitee.definition.model.v4.analytics.logging.Logging;
2121
import io.gravitee.gateway.report.guard.LogGuardService;
2222
import java.util.regex.Pattern;
23+
import java.util.regex.PatternSyntaxException;
24+
import java.util.stream.Stream;
25+
import lombok.Getter;
2326
import lombok.RequiredArgsConstructor;
27+
import lombok.Setter;
2428
import lombok.extern.slf4j.Slf4j;
2529

2630
/**
@@ -32,12 +36,20 @@
3236
public class LoggingContext implements ConditionSupplier {
3337

3438
private static final String DEFAULT_EXCLUDED_CONTENT_TYPES =
35-
"video.*|audio.*|image.*|application\\/octet-stream|application\\/pdf|text\\/event-stream";
39+
"video.*|audio.*|image.*|application/octet-stream|application/pdf|text/event-stream";
3640

3741
protected final Logging logging;
42+
43+
@Getter
3844
private int maxSizeLogMessage = -1;
45+
46+
@Setter
47+
@Getter
3948
private String excludedResponseTypes;
49+
4050
private Pattern excludedContentTypesPattern;
51+
52+
@Setter
4153
private LogGuardService logGuardService;
4254

4355
@Override
@@ -93,10 +105,6 @@ public boolean endpointResponsePayload() {
93105
return logging.getMode().isEndpoint() && logging.getPhase().isResponse() && logging.getContent().isPayload();
94106
}
95107

96-
public int getMaxSizeLogMessage() {
97-
return maxSizeLogMessage;
98-
}
99-
100108
/**
101109
* Define the max size of the logging (for each payload, whatever it's about the request / response / consumer / proxy)
102110
* Which means that if size is define to 5, it will be 5 x 4 = 20 (at most).
@@ -115,22 +123,24 @@ public void setMaxSizeLogMessage(String maxSizeLogMessage) {
115123
}
116124
}
117125

118-
public String getExcludedResponseTypes() {
119-
return excludedResponseTypes;
120-
}
121-
122-
public void setExcludedResponseTypes(final String excludedResponseTypes) {
123-
this.excludedResponseTypes = excludedResponseTypes;
124-
}
125-
126126
public boolean isContentTypeLoggable(final String contentType) {
127127
// init pattern
128128
if (excludedContentTypesPattern == null) {
129-
try {
130-
excludedContentTypesPattern = Pattern.compile(excludedResponseTypes);
131-
} catch (Exception e) {
132-
excludedContentTypesPattern = Pattern.compile(DEFAULT_EXCLUDED_CONTENT_TYPES);
133-
}
129+
this.excludedContentTypesPattern = Stream.of(
130+
logging.getOverrideContentTypeValidation(),
131+
excludedResponseTypes,
132+
DEFAULT_EXCLUDED_CONTENT_TYPES
133+
)
134+
.filter(pattern -> pattern != null && !pattern.isEmpty())
135+
.flatMap(pattern -> {
136+
try {
137+
return Stream.of(Pattern.compile(pattern));
138+
} catch (PatternSyntaxException e) {
139+
return Stream.empty();
140+
}
141+
})
142+
.findFirst()
143+
.orElse(null);
134144
}
135145

136146
return contentType == null || !excludedContentTypesPattern.matcher(contentType).find();
@@ -145,8 +155,4 @@ public boolean isContentTypeLoggable(final String contentType) {
145155
public boolean isBodyLoggable() {
146156
return logGuardService == null || !logGuardService.isLogGuardActive();
147157
}
148-
149-
public void setLogGuardService(LogGuardService logGuardService) {
150-
this.logGuardService = logGuardService;
151-
}
152158
}

gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/AbstractApiReactor.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,47 +15,30 @@
1515
*/
1616
package io.gravitee.gateway.reactive.handlers.api.v4;
1717

18-
import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_ENDPOINT_CONNECTOR_ID;
1918
import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_ENTRYPOINT_CONNECTOR;
2019
import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_EXECUTION_FAILURE;
21-
import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_INVOKER;
22-
import static io.gravitee.gateway.reactive.api.context.InternalContextAttributes.ATTR_INTERNAL_INVOKER_SKIP;
23-
import static io.reactivex.rxjava3.core.Completable.defer;
2420
import static io.reactivex.rxjava3.core.Observable.interval;
25-
import static java.lang.Boolean.TRUE;
2621

2722
import io.gravitee.common.component.AbstractLifecycleComponent;
2823
import io.gravitee.gateway.env.RequestTimeoutConfiguration;
2924
import io.gravitee.gateway.opentelemetry.TracingContext;
3025
import io.gravitee.gateway.reactive.api.ExecutionFailure;
3126
import io.gravitee.gateway.reactive.api.ExecutionPhase;
3227
import io.gravitee.gateway.reactive.api.connector.entrypoint.BaseEntrypointConnector;
33-
import io.gravitee.gateway.reactive.api.connector.entrypoint.EntrypointConnector;
34-
import io.gravitee.gateway.reactive.api.connector.entrypoint.HttpEntrypointConnector;
3528
import io.gravitee.gateway.reactive.api.context.ContextAttributes;
36-
import io.gravitee.gateway.reactive.api.context.InternalContextAttributes;
3729
import io.gravitee.gateway.reactive.api.context.base.BaseExecutionContext;
38-
import io.gravitee.gateway.reactive.api.context.tcp.TcpExecutionContext;
3930
import io.gravitee.gateway.reactive.api.hook.InvokerHook;
40-
import io.gravitee.gateway.reactive.api.invoker.BaseInvoker;
4131
import io.gravitee.gateway.reactive.api.invoker.HttpInvoker;
42-
import io.gravitee.gateway.reactive.api.invoker.Invoker;
43-
import io.gravitee.gateway.reactive.api.invoker.TcpInvoker;
4432
import io.gravitee.gateway.reactive.core.context.MutableExecutionContext;
45-
import io.gravitee.gateway.reactive.core.hook.HookHelper;
4633
import io.gravitee.gateway.reactive.core.v4.entrypoint.DefaultEntrypointConnectorResolver;
47-
import io.gravitee.gateway.reactive.handlers.api.adapter.invoker.InvokerAdapter;
4834
import io.gravitee.gateway.reactive.reactor.ApiReactor;
4935
import io.gravitee.gateway.reactor.handler.Acceptor;
5036
import io.gravitee.gateway.reactor.handler.ReactorHandler;
5137
import io.gravitee.node.api.configuration.Configuration;
52-
import io.gravitee.node.api.opentelemetry.Tracer;
53-
import io.gravitee.node.opentelemetry.OpenTelemetryFactory;
5438
import io.reactivex.rxjava3.core.Completable;
5539
import io.reactivex.rxjava3.schedulers.Schedulers;
5640
import java.util.ArrayList;
5741
import java.util.List;
58-
import java.util.Optional;
5942
import java.util.concurrent.TimeUnit;
6043
import java.util.concurrent.atomic.AtomicLong;
6144
import lombok.extern.slf4j.Slf4j;

gravitee-apim-gateway/gravitee-apim-gateway-handlers/gravitee-apim-gateway-handlers-api/src/main/java/io/gravitee/gateway/reactive/handlers/api/v4/analytics/logging/response/LogResponse.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ public void capture() {
6161

6262
@Override
6363
public void setHeaders(HttpHeaders headers) {
64-
if (headers instanceof LogHeadersCaptor) {
65-
super.setHeaders(((LogHeadersCaptor) headers).getCaptured());
64+
if (headers instanceof LogHeadersCaptor logHeadersCaptor) {
65+
super.setHeaders(logHeadersCaptor.getCaptured());
6666
} else {
6767
super.setHeaders(HttpHeaders.create(headers));
6868
}

0 commit comments

Comments
 (0)