blob: c8e3f5ee6837bcc748ffbd758bc818af00fd2434 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v1alpha3
import (
"strings"
"sync"
)
import (
accesslog "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3"
core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
fileaccesslog "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/file/v3"
cel "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/filters/cel/v3"
grpcaccesslog "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3"
otelaccesslog "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/open_telemetry/v3"
hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
tcp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
formatters "github.com/envoyproxy/go-control-plane/envoy/extensions/formatter/req_without_query/v3"
"github.com/envoyproxy/go-control-plane/pkg/wellknown"
otlpcommon "go.opentelemetry.io/proto/otlp/common/v1"
"google.golang.org/protobuf/types/known/structpb"
meshconfig "istio.io/api/mesh/v1alpha1"
"istio.io/pkg/log"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/util"
"github.com/apache/dubbo-go-pixiu/pkg/util/protomarshal"
)
const (
// EnvoyTextLogFormat format for envoy text based access logs for Istio 1.9 onwards.
// This includes the additional new operator RESPONSE_CODE_DETAILS and CONNECTION_TERMINATION_DETAILS that tells
// the reason why Envoy rejects a request.
EnvoyTextLogFormat = "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% " +
"%PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% " +
"%RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS% " +
"\"%UPSTREAM_TRANSPORT_FAILURE_REASON%\" %BYTES_RECEIVED% %BYTES_SENT% " +
"%DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" " +
"\"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\" " +
"%UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% " +
"%DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME%\n"
// EnvoyServerName for istio's envoy
EnvoyServerName = "istio-envoy"
httpEnvoyAccessLogFriendlyName = "http_envoy_accesslog"
tcpEnvoyAccessLogFriendlyName = "tcp_envoy_accesslog"
otelEnvoyAccessLogFriendlyName = "otel_envoy_accesslog"
listenerEnvoyAccessLogFriendlyName = "listener_envoy_accesslog"
tcpEnvoyALSName = "envoy.tcp_grpc_access_log"
otelEnvoyALSName = "envoy.access_loggers.open_telemetry"
// EnvoyAccessLogCluster is the cluster name that has details for server implementing Envoy ALS.
// This cluster is created in bootstrap.
EnvoyAccessLogCluster = "envoy_accesslog_service"
requestWithoutQuery = "%REQ_WITHOUT_QUERY"
devStdout = "/dev/stdout"
celFilter = "envoy.access_loggers.extension_filters.cel"
)
var (
// EnvoyJSONLogFormatIstio map of values for envoy json based access logs for Istio 1.9 onwards.
// This includes the additional log operator RESPONSE_CODE_DETAILS and CONNECTION_TERMINATION_DETAILS that tells
// the reason why Envoy rejects a request.
EnvoyJSONLogFormatIstio = &structpb.Struct{
Fields: map[string]*structpb.Value{
"start_time": {Kind: &structpb.Value_StringValue{StringValue: "%START_TIME%"}},
"route_name": {Kind: &structpb.Value_StringValue{StringValue: "%ROUTE_NAME%"}},
"method": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(:METHOD)%"}},
"path": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"}},
"protocol": {Kind: &structpb.Value_StringValue{StringValue: "%PROTOCOL%"}},
"response_code": {Kind: &structpb.Value_StringValue{StringValue: "%RESPONSE_CODE%"}},
"response_flags": {Kind: &structpb.Value_StringValue{StringValue: "%RESPONSE_FLAGS%"}},
"response_code_details": {Kind: &structpb.Value_StringValue{StringValue: "%RESPONSE_CODE_DETAILS%"}},
"connection_termination_details": {Kind: &structpb.Value_StringValue{StringValue: "%CONNECTION_TERMINATION_DETAILS%"}},
"bytes_received": {Kind: &structpb.Value_StringValue{StringValue: "%BYTES_RECEIVED%"}},
"bytes_sent": {Kind: &structpb.Value_StringValue{StringValue: "%BYTES_SENT%"}},
"duration": {Kind: &structpb.Value_StringValue{StringValue: "%DURATION%"}},
"upstream_service_time": {Kind: &structpb.Value_StringValue{StringValue: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%"}},
"x_forwarded_for": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-FORWARDED-FOR)%"}},
"user_agent": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(USER-AGENT)%"}},
"request_id": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-REQUEST-ID)%"}},
"authority": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(:AUTHORITY)%"}},
"upstream_host": {Kind: &structpb.Value_StringValue{StringValue: "%UPSTREAM_HOST%"}},
"upstream_cluster": {Kind: &structpb.Value_StringValue{StringValue: "%UPSTREAM_CLUSTER%"}},
"upstream_local_address": {Kind: &structpb.Value_StringValue{StringValue: "%UPSTREAM_LOCAL_ADDRESS%"}},
"downstream_local_address": {Kind: &structpb.Value_StringValue{StringValue: "%DOWNSTREAM_LOCAL_ADDRESS%"}},
"downstream_remote_address": {Kind: &structpb.Value_StringValue{StringValue: "%DOWNSTREAM_REMOTE_ADDRESS%"}},
"requested_server_name": {Kind: &structpb.Value_StringValue{StringValue: "%REQUESTED_SERVER_NAME%"}},
"upstream_transport_failure_reason": {Kind: &structpb.Value_StringValue{StringValue: "%UPSTREAM_TRANSPORT_FAILURE_REASON%"}},
},
}
// State logged by the metadata exchange filter about the upstream and downstream service instances
// We need to propagate these as part of access log service stream
// Logging them by default on the console may be an issue as the base64 encoded string is bound to be a big one.
// But end users can certainly configure it on their own via the meshConfig using the %FILTERSTATE% macro.
envoyWasmStateToLog = []string{"wasm.upstream_peer", "wasm.upstream_peer_id", "wasm.downstream_peer", "wasm.downstream_peer_id"}
// accessLogBuilder is used to set accessLog to filters
accessLogBuilder = newAccessLogBuilder()
// accessLogFormatters configures additional formatters needed for some of the format strings like "REQ_WITHOUT_QUERY"
accessLogFormatters = []*core.TypedExtensionConfig{
{
Name: "envoy.formatter.req_without_query",
TypedConfig: util.MessageToAny(&formatters.ReqWithoutQuery{}),
},
}
)
type AccessLogBuilder struct {
// tcpGrpcAccessLog is used when access log service is enabled in mesh config.
tcpGrpcAccessLog *accesslog.AccessLog
// httpGrpcAccessLog is used when access log service is enabled in mesh config.
httpGrpcAccessLog *accesslog.AccessLog
// tcpGrpcListenerAccessLog is used when access log service is enabled in mesh config.
tcpGrpcListenerAccessLog *accesslog.AccessLog
// file accessLog which is cached and reset on MeshConfig change.
mutex sync.RWMutex
fileAccesslog *accesslog.AccessLog
listenerFileAccessLog *accesslog.AccessLog
}
func newAccessLogBuilder() *AccessLogBuilder {
return &AccessLogBuilder{
tcpGrpcAccessLog: buildTCPGrpcAccessLog(false),
httpGrpcAccessLog: buildHTTPGrpcAccessLog(),
tcpGrpcListenerAccessLog: buildTCPGrpcAccessLog(true),
}
}
func (b *AccessLogBuilder) setTCPAccessLog(push *model.PushContext, proxy *model.Proxy, tcp *tcp.TcpProxy, class networking.ListenerClass) {
mesh := push.Mesh
cfg := push.Telemetry.AccessLogging(proxy, class)
if cfg == nil {
// No Telemetry API configured, fall back to legacy mesh config setting
if mesh.AccessLogFile != "" {
tcp.AccessLog = append(tcp.AccessLog, b.buildFileAccessLog(mesh))
}
if mesh.EnableEnvoyAccessLogService {
// Setting it to TCP as the low level one.
tcp.AccessLog = append(tcp.AccessLog, b.tcpGrpcAccessLog)
}
return
}
if al := buildAccessLogFromTelemetry(push, cfg, false); len(al) != 0 {
tcp.AccessLog = append(tcp.AccessLog, al...)
}
}
func buildAccessLogFromTelemetry(push *model.PushContext, spec *model.LoggingConfig, forListener bool) []*accesslog.AccessLog {
als := make([]*accesslog.AccessLog, 0)
telFilter := buildAccessLogFilterFromTelemetry(spec)
filters := []*accesslog.AccessLogFilter{}
if forListener {
filters = append(filters, addAccessLogFilter())
}
if telFilter != nil {
filters = append(filters, telFilter)
}
for _, p := range spec.Providers {
var al *accesslog.AccessLog
switch prov := p.Provider.(type) {
case *meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLog:
al = buildEnvoyFileAccessLogHelper(prov.EnvoyFileAccessLog)
case *meshconfig.MeshConfig_ExtensionProvider_EnvoyHttpAls:
al = buildHTTPGrpcAccessLogHelper(push, prov.EnvoyHttpAls)
case *meshconfig.MeshConfig_ExtensionProvider_EnvoyTcpAls:
al = buildTCPGrpcAccessLogHelper(push, prov.EnvoyTcpAls)
case *meshconfig.MeshConfig_ExtensionProvider_EnvoyOtelAls:
al = buildOpenTelemetryLogHelper(push, prov.EnvoyOtelAls)
}
if al == nil {
continue
}
al.Filter = buildAccessLogFilter(filters...)
als = append(als, al)
}
return als
}
func buildAccessLogFilterFromTelemetry(spec *model.LoggingConfig) *accesslog.AccessLogFilter {
if spec == nil || spec.Filter == nil {
return nil
}
fl := &cel.ExpressionFilter{
Expression: spec.Filter.Expression,
}
return &accesslog.AccessLogFilter{
FilterSpecifier: &accesslog.AccessLogFilter_ExtensionFilter{
ExtensionFilter: &accesslog.ExtensionFilter{
Name: celFilter,
ConfigType: &accesslog.ExtensionFilter_TypedConfig{TypedConfig: util.MessageToAny(fl)},
},
},
}
}
func (b *AccessLogBuilder) setHTTPAccessLog(push *model.PushContext, proxy *model.Proxy,
connectionManager *hcm.HttpConnectionManager, class networking.ListenerClass) {
mesh := push.Mesh
cfg := push.Telemetry.AccessLogging(proxy, class)
if cfg == nil {
// No Telemetry API configured, fall back to legacy mesh config setting
if mesh.AccessLogFile != "" {
connectionManager.AccessLog = append(connectionManager.AccessLog, b.buildFileAccessLog(mesh))
}
if mesh.EnableEnvoyAccessLogService {
connectionManager.AccessLog = append(connectionManager.AccessLog, b.httpGrpcAccessLog)
}
return
}
if al := buildAccessLogFromTelemetry(push, cfg, false); len(al) != 0 {
connectionManager.AccessLog = append(connectionManager.AccessLog, al...)
}
}
func (b *AccessLogBuilder) setListenerAccessLog(push *model.PushContext, proxy *model.Proxy,
listener *listener.Listener, class networking.ListenerClass) {
mesh := push.Mesh
if mesh.DisableEnvoyListenerLog {
return
}
cfg := push.Telemetry.AccessLogging(proxy, class)
if cfg == nil {
// No Telemetry API configured, fall back to legacy mesh config setting
if mesh.AccessLogFile != "" {
listener.AccessLog = append(listener.AccessLog, b.buildListenerFileAccessLog(mesh))
}
if mesh.EnableEnvoyAccessLogService {
// Setting it to TCP as the low level one.
listener.AccessLog = append(listener.AccessLog, b.tcpGrpcListenerAccessLog)
}
return
}
if al := buildAccessLogFromTelemetry(push, cfg, true); len(al) != 0 {
listener.AccessLog = append(listener.AccessLog, al...)
}
}
func buildTCPGrpcAccessLogHelper(push *model.PushContext, prov *meshconfig.MeshConfig_ExtensionProvider_EnvoyTcpGrpcV3LogProvider) *accesslog.AccessLog {
logName := tcpEnvoyAccessLogFriendlyName
if prov != nil && prov.LogName != "" {
logName = prov.LogName
}
filterObjects := envoyWasmStateToLog
if len(prov.FilterStateObjectsToLog) != 0 {
filterObjects = prov.FilterStateObjectsToLog
}
hostname, cluster, err := clusterLookupFn(push, prov.Service, int(prov.Port))
if err != nil {
log.Errorf("could not find cluster for tcp grpc provider %q: %v", prov, err)
return nil
}
fl := &grpcaccesslog.TcpGrpcAccessLogConfig{
CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{
LogName: logName,
GrpcService: &core.GrpcService{
TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
EnvoyGrpc: &core.GrpcService_EnvoyGrpc{
ClusterName: cluster,
Authority: hostname,
},
},
},
TransportApiVersion: core.ApiVersion_V3,
FilterStateObjectsToLog: filterObjects,
},
}
return &accesslog.AccessLog{
Name: tcpEnvoyALSName,
ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: util.MessageToAny(fl)},
}
}
func buildEnvoyFileAccessLogHelper(prov *meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider) *accesslog.AccessLog {
p := prov.Path
if p == "" {
p = devStdout
}
fl := &fileaccesslog.FileAccessLog{
Path: p,
}
needsFormatter := false
if prov.LogFormat != nil {
switch logFormat := prov.LogFormat.LogFormat.(type) {
case *meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider_LogFormat_Text:
fl.AccessLogFormat, needsFormatter = buildFileAccessTextLogFormat(logFormat.Text)
case *meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider_LogFormat_Labels:
fl.AccessLogFormat, needsFormatter = buildFileAccessJSONLogFormat(logFormat)
}
} else {
fl.AccessLogFormat, needsFormatter = buildFileAccessTextLogFormat("")
}
if needsFormatter {
fl.GetLogFormat().Formatters = accessLogFormatters
}
al := &accesslog.AccessLog{
Name: wellknown.FileAccessLog,
ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: util.MessageToAny(fl)},
}
return al
}
func buildFileAccessTextLogFormat(text string) (*fileaccesslog.FileAccessLog_LogFormat, bool) {
formatString := EnvoyTextLogFormat
if text != "" {
formatString = text
}
needsFormatter := strings.Contains(formatString, requestWithoutQuery)
return &fileaccesslog.FileAccessLog_LogFormat{
LogFormat: &core.SubstitutionFormatString{
Format: &core.SubstitutionFormatString_TextFormatSource{
TextFormatSource: &core.DataSource{
Specifier: &core.DataSource_InlineString{
InlineString: formatString,
},
},
},
},
}, needsFormatter
}
func buildFileAccessJSONLogFormat(
logFormat *meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider_LogFormat_Labels) (*fileaccesslog.FileAccessLog_LogFormat, bool) {
jsonLogStruct := EnvoyJSONLogFormatIstio
if logFormat.Labels != nil {
jsonLogStruct = logFormat.Labels
}
// allow default behavior when no labels supplied.
if len(jsonLogStruct.Fields) == 0 {
jsonLogStruct = EnvoyJSONLogFormatIstio
}
needsFormatter := false
for _, value := range jsonLogStruct.Fields {
if value.GetStringValue() == requestWithoutQuery {
needsFormatter = true
break
}
}
return &fileaccesslog.FileAccessLog_LogFormat{
LogFormat: &core.SubstitutionFormatString{
Format: &core.SubstitutionFormatString_JsonFormat{
JsonFormat: jsonLogStruct,
},
},
}, needsFormatter
}
func buildHTTPGrpcAccessLogHelper(push *model.PushContext, prov *meshconfig.MeshConfig_ExtensionProvider_EnvoyHttpGrpcV3LogProvider) *accesslog.AccessLog {
logName := httpEnvoyAccessLogFriendlyName
if prov != nil && prov.LogName != "" {
logName = prov.LogName
}
filterObjects := envoyWasmStateToLog
if len(prov.FilterStateObjectsToLog) != 0 {
filterObjects = prov.FilterStateObjectsToLog
}
hostname, cluster, err := clusterLookupFn(push, prov.Service, int(prov.Port))
if err != nil {
log.Errorf("could not find cluster for http grpc provider %q: %v", prov, err)
return nil
}
fl := &grpcaccesslog.HttpGrpcAccessLogConfig{
CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{
LogName: logName,
GrpcService: &core.GrpcService{
TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
EnvoyGrpc: &core.GrpcService_EnvoyGrpc{
ClusterName: cluster,
Authority: hostname,
},
},
},
TransportApiVersion: core.ApiVersion_V3,
FilterStateObjectsToLog: filterObjects,
},
AdditionalRequestHeadersToLog: prov.AdditionalRequestHeadersToLog,
AdditionalResponseHeadersToLog: prov.AdditionalResponseHeadersToLog,
AdditionalResponseTrailersToLog: prov.AdditionalResponseTrailersToLog,
}
return &accesslog.AccessLog{
Name: wellknown.HTTPGRPCAccessLog,
ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: util.MessageToAny(fl)},
}
}
func buildFileAccessLogHelper(path string, mesh *meshconfig.MeshConfig) *accesslog.AccessLog {
// We need to build access log. This is needed either on first access or when mesh config changes.
fl := &fileaccesslog.FileAccessLog{
Path: path,
}
needsFormatter := false
switch mesh.AccessLogEncoding {
case meshconfig.MeshConfig_TEXT:
formatString := EnvoyTextLogFormat
if mesh.AccessLogFormat != "" {
formatString = mesh.AccessLogFormat
}
needsFormatter = strings.Contains(formatString, requestWithoutQuery)
fl.AccessLogFormat = &fileaccesslog.FileAccessLog_LogFormat{
LogFormat: &core.SubstitutionFormatString{
Format: &core.SubstitutionFormatString_TextFormatSource{
TextFormatSource: &core.DataSource{
Specifier: &core.DataSource_InlineString{
InlineString: formatString,
},
},
},
},
}
case meshconfig.MeshConfig_JSON:
jsonLogStruct := EnvoyJSONLogFormatIstio
if len(mesh.AccessLogFormat) > 0 {
parsedJSONLogStruct := structpb.Struct{}
if err := protomarshal.UnmarshalAllowUnknown([]byte(mesh.AccessLogFormat), &parsedJSONLogStruct); err != nil {
log.Errorf("error parsing provided json log format, default log format will be used: %v", err)
} else {
jsonLogStruct = &parsedJSONLogStruct
}
}
for _, value := range jsonLogStruct.Fields {
if value.GetStringValue() == requestWithoutQuery {
needsFormatter = true
break
}
}
fl.AccessLogFormat = &fileaccesslog.FileAccessLog_LogFormat{
LogFormat: &core.SubstitutionFormatString{
Format: &core.SubstitutionFormatString_JsonFormat{
JsonFormat: jsonLogStruct,
},
},
}
default:
log.Warnf("unsupported access log format %v", mesh.AccessLogEncoding)
}
if needsFormatter {
fl.GetLogFormat().Formatters = accessLogFormatters
}
al := &accesslog.AccessLog{
Name: wellknown.FileAccessLog,
ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: util.MessageToAny(fl)},
}
return al
}
func buildOpenTelemetryLogHelper(pushCtx *model.PushContext,
provider *meshconfig.MeshConfig_ExtensionProvider_EnvoyOpenTelemetryLogProvider,
) *accesslog.AccessLog {
hostname, cluster, err := clusterLookupFn(pushCtx, provider.Service, int(provider.Port))
if err != nil {
log.Errorf("could not find cluster for open telemetry provider %q: %v", provider, err)
return nil
}
logName := provider.LogName
if logName == "" {
logName = otelEnvoyAccessLogFriendlyName
}
f := EnvoyTextLogFormat
if provider.LogFormat != nil && provider.LogFormat.Text != "" {
f = provider.LogFormat.Text
}
var labels *structpb.Struct
if provider.LogFormat != nil {
labels = provider.LogFormat.Labels
}
cfg := buildOpenTelemetryAccessLogConfig(logName, hostname, cluster, f, labels)
return &accesslog.AccessLog{
Name: otelEnvoyALSName,
ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: util.MessageToAny(cfg)},
}
}
func buildOpenTelemetryAccessLogConfig(logName, hostname, clusterName, format string, labels *structpb.Struct) *otelaccesslog.OpenTelemetryAccessLogConfig {
cfg := &otelaccesslog.OpenTelemetryAccessLogConfig{
CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{
LogName: logName,
GrpcService: &core.GrpcService{
TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
EnvoyGrpc: &core.GrpcService_EnvoyGrpc{
ClusterName: clusterName,
Authority: hostname,
},
},
},
TransportApiVersion: core.ApiVersion_V3,
FilterStateObjectsToLog: envoyWasmStateToLog,
},
}
if format != "" {
cfg.Body = &otlpcommon.AnyValue{
Value: &otlpcommon.AnyValue_StringValue{
StringValue: format,
},
}
}
if labels != nil && len(labels.Fields) != 0 {
cfg.Attributes = &otlpcommon.KeyValueList{
Values: convertStructToAttributeKeyValues(labels.Fields),
}
}
return cfg
}
func convertStructToAttributeKeyValues(labels map[string]*structpb.Value) []*otlpcommon.KeyValue {
if len(labels) == 0 {
return nil
}
attrList := make([]*otlpcommon.KeyValue, 0, len(labels))
for key, value := range labels {
kv := &otlpcommon.KeyValue{
Key: key,
Value: &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: value.GetStringValue()}},
}
attrList = append(attrList, kv)
}
return attrList
}
func (b *AccessLogBuilder) buildFileAccessLog(mesh *meshconfig.MeshConfig) *accesslog.AccessLog {
if cal := b.cachedFileAccessLog(); cal != nil {
return cal
}
// We need to build access log. This is needed either on first access or when mesh config changes.
al := buildFileAccessLogHelper(mesh.AccessLogFile, mesh)
b.mutex.Lock()
defer b.mutex.Unlock()
b.fileAccesslog = al
return al
}
func addAccessLogFilter() *accesslog.AccessLogFilter {
return &accesslog.AccessLogFilter{
FilterSpecifier: &accesslog.AccessLogFilter_ResponseFlagFilter{
ResponseFlagFilter: &accesslog.ResponseFlagFilter{Flags: []string{"NR"}},
},
}
}
func buildAccessLogFilter(f ...*accesslog.AccessLogFilter) *accesslog.AccessLogFilter {
if len(f) == 0 {
return nil
}
if len(f) == 1 {
return f[0]
}
return &accesslog.AccessLogFilter{
FilterSpecifier: &accesslog.AccessLogFilter_AndFilter{
AndFilter: &accesslog.AndFilter{
Filters: f,
},
},
}
}
func (b *AccessLogBuilder) buildListenerFileAccessLog(mesh *meshconfig.MeshConfig) *accesslog.AccessLog {
if cal := b.cachedListenerFileAccessLog(); cal != nil {
return cal
}
// We need to build access log. This is needed either on first access or when mesh config changes.
lal := buildFileAccessLogHelper(mesh.AccessLogFile, mesh)
// We add ResponseFlagFilter here, as we want to get listener access logs only on scenarios where we might
// not get filter Access Logs like in cases like NR to upstream.
lal.Filter = addAccessLogFilter()
b.mutex.Lock()
defer b.mutex.Unlock()
b.listenerFileAccessLog = lal
return lal
}
func (b *AccessLogBuilder) cachedFileAccessLog() *accesslog.AccessLog {
b.mutex.RLock()
defer b.mutex.RUnlock()
return b.fileAccesslog
}
func (b *AccessLogBuilder) cachedListenerFileAccessLog() *accesslog.AccessLog {
b.mutex.RLock()
defer b.mutex.RUnlock()
return b.listenerFileAccessLog
}
func buildTCPGrpcAccessLog(isListener bool) *accesslog.AccessLog {
accessLogFriendlyName := tcpEnvoyAccessLogFriendlyName
if isListener {
accessLogFriendlyName = listenerEnvoyAccessLogFriendlyName
}
fl := &grpcaccesslog.TcpGrpcAccessLogConfig{
CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{
LogName: accessLogFriendlyName,
GrpcService: &core.GrpcService{
TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
EnvoyGrpc: &core.GrpcService_EnvoyGrpc{
ClusterName: EnvoyAccessLogCluster,
},
},
},
TransportApiVersion: core.ApiVersion_V3,
FilterStateObjectsToLog: envoyWasmStateToLog,
},
}
var filter *accesslog.AccessLogFilter
if isListener {
filter = addAccessLogFilter()
}
return &accesslog.AccessLog{
Name: tcpEnvoyALSName,
ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: util.MessageToAny(fl)},
Filter: filter,
}
}
func buildHTTPGrpcAccessLog() *accesslog.AccessLog {
fl := &grpcaccesslog.HttpGrpcAccessLogConfig{
CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{
LogName: httpEnvoyAccessLogFriendlyName,
GrpcService: &core.GrpcService{
TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
EnvoyGrpc: &core.GrpcService_EnvoyGrpc{
ClusterName: EnvoyAccessLogCluster,
},
},
},
TransportApiVersion: core.ApiVersion_V3,
FilterStateObjectsToLog: envoyWasmStateToLog,
},
}
return &accesslog.AccessLog{
Name: wellknown.HTTPGRPCAccessLog,
ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: util.MessageToAny(fl)},
}
}
func (b *AccessLogBuilder) reset() {
b.mutex.Lock()
b.fileAccesslog = nil
b.listenerFileAccessLog = nil
b.mutex.Unlock()
}