Skip to content

Commit 1eed84e

Browse files
sivakami-projectsSivakami Subramaniam
and
Sivakami Subramaniam
authored
ETW logging implementation in CNI (#2668)
* Add ETW support in zap logger for CNI Added a zap WriteSyncer to enable direct ETW logging through zap core, maintaining existing logging structure while to ETW from CNI. * Transform 'zapetw' package into a standalone module for external use * Revert "Transform 'zapetw' package into a standalone module for external use" This reverts commit 63050ed. * Relocate EtwWriteSyncer.go to zapetw module for improved organization - Renamed and moved cni/log/ETWZapCore/EtwWriteSyncer.go to zapetw/write_syncer.go. * Applied gofumpt formatting to adhere to style guidelines. * 1. Implemented platform-specific ETW logging enhancements. 2. Refactor ETW initialization into dedicated method and zapetw package. * Changed InitETWLogger method signature for Linux. * Wrapped error messages at each level of the call hierarchy. * Removed punctuation marks from error messages. * Wrapped error messages with errors.wrap method. * Added comments for clarity. * implemented zap.core for ETW. * Fixed lint issues. * Catch errors from etw.writeEvent method. * Renamed provider. * Abstracted etw core creation in logger_windows. Removed unsupported error from logger_linux to keep the behaviour uniform. * renamed unused parameter. * Renamed variable to lower camel case as it is private. Removed additional local reference. * fixed variable name. * Added comment. * Renamed ETW provider, removed application names from the provider name. --------- Co-authored-by: Sivakami Subramaniam <[email protected]>
1 parent 1f01781 commit 1eed84e

File tree

4 files changed

+140
-6
lines changed

4 files changed

+140
-6
lines changed

cni/log/logger.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ var (
1717
const (
1818
maxLogFileSizeInMb = 5
1919
maxLogFileCount = 8
20+
etwCNIEventName = "Azure-CNI"
21+
loggingLevel = zapcore.DebugLevel
2022
)
2123

2224
func initZapLog(logFile string) *zap.Logger {
@@ -30,13 +32,17 @@ func initZapLog(logFile string) *zap.Logger {
3032
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
3133
jsonEncoder := zapcore.NewJSONEncoder(encoderConfig)
3234

33-
core := zapcore.NewCore(jsonEncoder, logFileCNIWriter, zapcore.DebugLevel)
34-
Logger := zap.New(core)
35-
return Logger
35+
textFileCore := zapcore.NewCore(jsonEncoder, logFileCNIWriter, loggingLevel)
36+
core, err := JoinPlatformCores(textFileCore, loggingLevel)
37+
if err != nil {
38+
// If we fail to join the platform cores, fallback to the original core.
39+
core = textFileCore
40+
}
41+
return zap.New(core, zap.AddCaller()).With(zap.Int("pid", os.Getpid()))
3642
}
3743

3844
var (
39-
CNILogger = initZapLog(zapCNILogFile).With(zap.Int("pid", os.Getpid()))
40-
IPamLogger = initZapLog(zapIpamLogFile).With(zap.Int("pid", os.Getpid()))
41-
TelemetryLogger = initZapLog(zapTelemetryLogFile).With(zap.Int("pid", os.Getpid()))
45+
CNILogger = initZapLog(zapCNILogFile)
46+
IPamLogger = initZapLog(zapIpamLogFile)
47+
TelemetryLogger = initZapLog(zapTelemetryLogFile)
4248
)

cni/log/logger_linux.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
package log
22

3+
import (
4+
"go.uber.org/zap/zapcore"
5+
)
6+
37
const (
48
// LogPath is the path where log files are stored.
59
LogPath = "/var/log/"
610
)
11+
12+
func JoinPlatformCores(c zapcore.Core, _ zapcore.Level) (zapcore.Core, error) {
13+
return c, nil
14+
}

cni/log/logger_windows.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,34 @@
11
package log
22

3+
import (
4+
"github.com/Azure/azure-container-networking/zapetw"
5+
"github.com/pkg/errors"
6+
"go.uber.org/zap"
7+
"go.uber.org/zap/zapcore"
8+
)
9+
310
const (
411
// LogPath is the path where log files are stored.
512
LogPath = ""
613
)
14+
15+
func JoinPlatformCores(core zapcore.Core, loggingLevel zapcore.Level) (zapcore.Core, error) {
16+
etwcore, err := etwCore(loggingLevel)
17+
if err != nil {
18+
return core, err
19+
}
20+
teecore := zapcore.NewTee(core, etwcore)
21+
return teecore, nil
22+
}
23+
24+
func etwCore(loggingLevel zapcore.Level) (zapcore.Core, error) {
25+
encoderConfig := zap.NewProductionEncoderConfig()
26+
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
27+
jsonEncoder := zapcore.NewJSONEncoder(encoderConfig)
28+
29+
etwcore, err := zapetw.NewETWCore(etwCNIEventName, jsonEncoder, loggingLevel)
30+
if err != nil {
31+
return nil, errors.Wrap(err, "failed to create ETW core")
32+
}
33+
return etwcore, nil
34+
}

zapetw/core_windows.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package zapetw
2+
3+
import (
4+
"github.com/Microsoft/go-winio/pkg/etw"
5+
"github.com/pkg/errors"
6+
"go.uber.org/zap/zapcore"
7+
)
8+
9+
// <product_name>-<component_name>
10+
const providername = "ACN-Monitoring"
11+
12+
type ETWCore struct {
13+
provider *etw.Provider
14+
eventName string
15+
encoder zapcore.Encoder
16+
fields []zapcore.Field
17+
zapcore.LevelEnabler
18+
}
19+
20+
func NewETWCore(eventName string, encoder zapcore.Encoder, levelEnabler zapcore.LevelEnabler) (*ETWCore, error) {
21+
provider, err := etw.NewProviderWithOptions(providername)
22+
if err != nil {
23+
return nil, errors.Wrap(err, "failed to create ETW provider")
24+
}
25+
return &ETWCore{
26+
provider: provider,
27+
eventName: eventName,
28+
encoder: encoder,
29+
LevelEnabler: levelEnabler,
30+
}, nil
31+
}
32+
33+
func (core *ETWCore) With(fields []zapcore.Field) zapcore.Core {
34+
return &ETWCore{
35+
provider: core.provider,
36+
eventName: core.eventName,
37+
encoder: core.encoder,
38+
LevelEnabler: core.LevelEnabler,
39+
fields: append(core.fields, fields...),
40+
}
41+
}
42+
43+
// Check is an implementation of the zapcore.Core interface's Check method.
44+
// Check determines whether the logger core is enabled at the supplied zapcore.Entry's Level.
45+
// If enabled, it adds the core to the CheckedEntry and returns it, otherwise returns the CheckedEntry unchanged.
46+
func (core *ETWCore) Check(entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry) *zapcore.CheckedEntry {
47+
if core.Enabled(entry.Level) {
48+
return checkedEntry.AddCore(entry, core)
49+
}
50+
return checkedEntry
51+
}
52+
53+
func (core *ETWCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
54+
etwLevel := zapLevelToETWLevel(entry.Level)
55+
56+
buffer, err := core.encoder.EncodeEntry(entry, fields)
57+
if err != nil {
58+
return errors.Wrap(err, "failed to encode entry")
59+
}
60+
61+
err = core.provider.WriteEvent(
62+
core.eventName,
63+
[]etw.EventOpt{etw.WithLevel(etwLevel)},
64+
[]etw.FieldOpt{etw.StringField("Message", buffer.String())},
65+
)
66+
if err != nil {
67+
return errors.Wrap(err, "failed to write event")
68+
}
69+
70+
return nil
71+
}
72+
73+
func (core *ETWCore) Sync() error {
74+
return nil
75+
}
76+
77+
func zapLevelToETWLevel(level zapcore.Level) etw.Level {
78+
switch level {
79+
case zapcore.DebugLevel:
80+
return etw.LevelVerbose // ETW doesn't have a Debug level, so Verbose is used instead.
81+
case zapcore.InfoLevel:
82+
return etw.LevelInfo
83+
case zapcore.WarnLevel:
84+
return etw.LevelWarning
85+
case zapcore.ErrorLevel:
86+
return etw.LevelError
87+
case zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel, zapcore.InvalidLevel:
88+
return etw.LevelCritical
89+
default:
90+
return etw.LevelAlways
91+
}
92+
}

0 commit comments

Comments
 (0)