-
Notifications
You must be signed in to change notification settings - Fork 32
Add log type include filter #714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a5b1ad3
a2088ec
a17e86b
8da9a70
7fc1b84
b961719
110fb27
1820deb
3447483
1e96b21
bb0368a
2dfc90f
95ff14f
6cda574
d0ae8a3
db8afe4
31b2c90
91407bd
1f98d37
e9f1e1b
a3eda41
31b219b
d7e830c
0775898
3ba2a27
29c6830
ecab32a
a312cd4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,6 +53,178 @@ var _ = Describe("Filtering Drain Writer", func() { | |
| _, err := syslog.NewFilteringDrainWriter(binding, &fakeWriter{}) | ||
| Expect(err).To(HaveOccurred()) | ||
| }) | ||
|
|
||
| Context("when source_type tag is missing", func() { | ||
| var envelope *loggregator_v2.Envelope | ||
|
|
||
| BeforeEach(func() { | ||
| envelope = &loggregator_v2.Envelope{ | ||
| Message: &loggregator_v2.Envelope_Log{ | ||
| Log: &loggregator_v2.Log{ | ||
| Payload: []byte("test log"), | ||
| }, | ||
| }, | ||
| Tags: map[string]string{ | ||
| // source_type tag is intentionally missing | ||
| }, | ||
| } | ||
| }) | ||
|
|
||
| It("filters out the log with missing source_type when include filter is configured", func() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The filter works based on the values of the Is this behavior documented somewhere?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No it is not. The best place for that is likely https://docs.cloudfoundry.org/devguide/services/log-management.html ?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's the place. Please open a PR. Btw, why is the description of the parameters already in the docs?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Opened up a new PR at cloudfoundry/docs-dev-guide#584
The previous PR has been merged a bit sooner than expected. |
||
| binding := syslog.Binding{ | ||
| DrainData: syslog.LOGS, | ||
| LogFilter: syslog.NewLogFilter(syslog.LogSourceTypeSet{syslog.LOG_SOURCE_APP: struct{}{}}, syslog.LogFilterModeInclude), | ||
| } | ||
| fakeWriter := &fakeWriter{} | ||
| drainWriter, err := syslog.NewFilteringDrainWriter(binding, fakeWriter) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| err = drainWriter.Write(envelope) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(fakeWriter.received).To(Equal(0)) | ||
|
|
||
| appEnvelope := &loggregator_v2.Envelope{ | ||
| Message: &loggregator_v2.Envelope_Log{ | ||
| Log: &loggregator_v2.Log{Payload: []byte("app log")}, | ||
| }, | ||
| Tags: map[string]string{"source_type": "APP/PROC/WEB/0"}, | ||
| } | ||
| err = drainWriter.Write(appEnvelope) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(fakeWriter.received).To(Equal(1)) | ||
| }) | ||
|
|
||
| It("sends the log with missing source_type when exclude filter is configured", func() { | ||
jorbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| binding := syslog.Binding{ | ||
| DrainData: syslog.LOGS, | ||
| LogFilter: syslog.NewLogFilter(syslog.LogSourceTypeSet{syslog.LOG_SOURCE_RTR: struct{}{}}, syslog.LogFilterModeExclude), | ||
| } | ||
| fakeWriter := &fakeWriter{} | ||
| drainWriter, err := syslog.NewFilteringDrainWriter(binding, fakeWriter) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| err = drainWriter.Write(envelope) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(fakeWriter.received).To(Equal(1)) | ||
|
|
||
| rtrEnvelope := &loggregator_v2.Envelope{ | ||
| Message: &loggregator_v2.Envelope_Log{ | ||
| Log: &loggregator_v2.Log{Payload: []byte("rtr log")}, | ||
| }, | ||
| Tags: map[string]string{"source_type": "RTR/1"}, | ||
| } | ||
| err = drainWriter.Write(rtrEnvelope) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(fakeWriter.received).To(Equal(1)) | ||
| }) | ||
| }) | ||
jorbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Context("when source_type tag is present", func() { | ||
| It("filters logs based on include filter - includes only APP logs", func() { | ||
| binding := syslog.Binding{ | ||
| DrainData: syslog.LOGS, | ||
| LogFilter: syslog.NewLogFilter(syslog.LogSourceTypeSet{syslog.LOG_SOURCE_APP: struct{}{}}, syslog.LogFilterModeInclude), | ||
| } | ||
| fakeWriter := &fakeWriter{} | ||
| drainWriter, err := syslog.NewFilteringDrainWriter(binding, fakeWriter) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| envelopes := []*loggregator_v2.Envelope{ | ||
| { | ||
| Message: &loggregator_v2.Envelope_Log{ | ||
| Log: &loggregator_v2.Log{Payload: []byte("app log")}, | ||
| }, | ||
| Tags: map[string]string{"source_type": "APP/PROC/WEB/0"}, | ||
| }, | ||
| { | ||
| Message: &loggregator_v2.Envelope_Log{ | ||
| Log: &loggregator_v2.Log{Payload: []byte("rtr log")}, | ||
| }, | ||
| Tags: map[string]string{"source_type": "RTR/1"}, | ||
| }, | ||
| { | ||
| Message: &loggregator_v2.Envelope_Log{ | ||
| Log: &loggregator_v2.Log{Payload: []byte("stg log")}, | ||
| }, | ||
| Tags: map[string]string{"source_type": "STG/0"}, | ||
| }, | ||
| } | ||
|
|
||
| for _, envelope := range envelopes { | ||
| err = drainWriter.Write(envelope) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| } | ||
|
|
||
| // Only APP log should be sent | ||
| Expect(fakeWriter.received).To(Equal(1)) | ||
| }) | ||
|
|
||
| It("filters logs based on exclude filter - excludes RTR logs", func() { | ||
| binding := syslog.Binding{ | ||
| DrainData: syslog.LOGS, | ||
| LogFilter: syslog.NewLogFilter(syslog.LogSourceTypeSet{syslog.LOG_SOURCE_RTR: struct{}{}}, syslog.LogFilterModeExclude), | ||
| } | ||
| fakeWriter := &fakeWriter{} | ||
| drainWriter, err := syslog.NewFilteringDrainWriter(binding, fakeWriter) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| envelopes := []*loggregator_v2.Envelope{ | ||
| { | ||
| Message: &loggregator_v2.Envelope_Log{ | ||
| Log: &loggregator_v2.Log{Payload: []byte("app log")}, | ||
| }, | ||
| Tags: map[string]string{"source_type": "APP/PROC/WEB/0"}, | ||
| }, | ||
| { | ||
| Message: &loggregator_v2.Envelope_Log{ | ||
| Log: &loggregator_v2.Log{Payload: []byte("rtr log")}, | ||
| }, | ||
| Tags: map[string]string{"source_type": "RTR/1"}, | ||
| }, | ||
| { | ||
| Message: &loggregator_v2.Envelope_Log{ | ||
| Log: &loggregator_v2.Log{Payload: []byte("stg log")}, | ||
| }, | ||
| Tags: map[string]string{"source_type": "STG/0"}, | ||
| }, | ||
| } | ||
|
|
||
| for _, envelope := range envelopes { | ||
| err = drainWriter.Write(envelope) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| } | ||
|
|
||
| // APP and STG logs should be sent, RTR should be filtered out | ||
| Expect(fakeWriter.received).To(Equal(2)) | ||
| }) | ||
|
|
||
| It("sends logs with unknown source_type prefix when filter is set", func() { | ||
| binding := syslog.Binding{ | ||
| DrainData: syslog.LOGS, | ||
| LogFilter: syslog.NewLogFilter(syslog.LogSourceTypeSet{syslog.LOG_SOURCE_APP: struct{}{}}, syslog.LogFilterModeExclude), | ||
| } | ||
| fakeWriter := &fakeWriter{} | ||
| drainWriter, err := syslog.NewFilteringDrainWriter(binding, fakeWriter) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| envelope := &loggregator_v2.Envelope{ | ||
| Message: &loggregator_v2.Envelope_Log{ | ||
| Log: &loggregator_v2.Log{ | ||
| Payload: []byte("test log"), | ||
| }, | ||
| }, | ||
| Tags: map[string]string{ | ||
| "source_type": "UNKNOWN/some/path", | ||
| }, | ||
| } | ||
|
|
||
| err = drainWriter.Write(envelope) | ||
|
|
||
| // Should send the log because unknown types default to being included for exclude filter | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(fakeWriter.received).To(Equal(1)) | ||
| }) | ||
| }) | ||
chombium marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }) | ||
|
|
||
| type fakeWriter struct { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| package syslog | ||
jorbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| import "strings" | ||
|
|
||
| // LogSourceType defines the source types used within Cloud Foundry | ||
| // Their order in the code is as documented in https://docs.cloudfoundry.org/devguide/deploy-apps/streaming-logs.html#format | ||
| type LogSourceType string | ||
|
|
||
| const ( | ||
| LOG_SOURCE_API LogSourceType = "API" | ||
| LOG_SOURCE_STG LogSourceType = "STG" | ||
| LOG_SOURCE_RTR LogSourceType = "RTR" | ||
| LOG_SOURCE_LGR LogSourceType = "LGR" | ||
| LOG_SOURCE_APP LogSourceType = "APP" | ||
| LOG_SOURCE_SSH LogSourceType = "SSH" | ||
| LOG_SOURCE_CELL LogSourceType = "CELL" | ||
| LOG_SOURCE_PROXY LogSourceType = "PROXY" | ||
| LOG_SOURCE_HEALTH LogSourceType = "HEALTH" | ||
| LOG_SOURCE_SYS LogSourceType = "SYS" | ||
| LOG_SOURCE_STATS LogSourceType = "STATS" | ||
| ) | ||
|
|
||
| // validSourceTypes contains SourceType prefixes for efficient lookup | ||
| var validSourceTypes = map[LogSourceType]struct{}{ | ||
| LOG_SOURCE_API: {}, | ||
| LOG_SOURCE_STG: {}, | ||
| LOG_SOURCE_RTR: {}, | ||
| LOG_SOURCE_LGR: {}, | ||
| LOG_SOURCE_APP: {}, | ||
| LOG_SOURCE_SSH: {}, | ||
| LOG_SOURCE_CELL: {}, | ||
| } | ||
|
|
||
| // IsValid checks if the provided SourceType is valid | ||
| func (lt LogSourceType) IsValid() bool { | ||
| _, ok := validSourceTypes[lt] | ||
| return ok | ||
| } | ||
|
|
||
| // ParseSourceType parses a string into a SourceType value | ||
| func ParseSourceType(s string) (LogSourceType, bool) { | ||
| lt := LogSourceType(strings.ToUpper(s)) | ||
| return lt, lt.IsValid() | ||
| } | ||
|
|
||
| // ParseSourceTypeList parses a comma-separated list of source types and returns | ||
| // the valid types as a set and any unknown types as a slice. | ||
| func ParseSourceTypeList(sourceTypeList string) (LogSourceTypeSet, []string) { | ||
| sourceTypes := strings.Split(sourceTypeList, ",") | ||
| set := make(LogSourceTypeSet, len(sourceTypes)) | ||
| var unknownTypes []string | ||
|
|
||
| for _, sourceType := range sourceTypes { | ||
| t, ok := ParseSourceType(sourceType) | ||
| if !ok { | ||
| unknownTypes = append(unknownTypes, sourceType) | ||
| continue | ||
| } | ||
| set.Add(t) | ||
| } | ||
|
|
||
| return set, unknownTypes | ||
| } | ||
|
|
||
| // ExtractPrefix extracts the prefix from a source_type tag (e.g., "APP/PROC/WEB/0" -> "APP") | ||
| func ExtractPrefix(sourceTypeTag string) string { | ||
| if idx := strings.IndexByte(sourceTypeTag, '/'); idx != -1 { | ||
| return sourceTypeTag[:idx] | ||
| } | ||
| return sourceTypeTag | ||
| } | ||
|
|
||
| // LogSourceTypeSet is a set of SourceTypes for efficient membership checking | ||
| type LogSourceTypeSet map[LogSourceType]struct{} | ||
|
|
||
| // Add adds a SourceType to the set | ||
| func (s LogSourceTypeSet) Add(lt LogSourceType) { | ||
| s[lt] = struct{}{} | ||
| } | ||
|
|
||
| // Contains checks if the set contains a SourceType | ||
| func (s LogSourceTypeSet) Contains(lt LogSourceType) bool { | ||
| _, exists := s[lt] | ||
| return exists | ||
| } | ||
|
|
||
| // LogFilterMode determines how the log filter should be applied | ||
| type LogFilterMode int | ||
|
|
||
| const ( | ||
| // LogFilterModeInclude only includes logs matching the specified types (strict) | ||
| LogFilterModeInclude LogFilterMode = iota | ||
| // LogFilterModeExclude excludes logs matching the specified types (permissive) | ||
| LogFilterModeExclude | ||
| ) | ||
|
|
||
| // LogFilter encapsulates source type filtering configuration | ||
| type LogFilter struct { | ||
| Types LogSourceTypeSet | ||
| Mode LogFilterMode | ||
| } | ||
|
|
||
| // NewLogFilter creates a new LogFilter with the given types and mode | ||
| func NewLogFilter(types LogSourceTypeSet, mode LogFilterMode) *LogFilter { | ||
| return &LogFilter{ | ||
| Types: types, | ||
| Mode: mode, | ||
| } | ||
| } | ||
|
|
||
| // ShouldInclude determines if a log with the given sourceTypeTag should be forwarded | ||
| // Include mode omits missing/unknown source types, exclude mode forwards them | ||
| func (f *LogFilter) ShouldInclude(sourceTypeTag string) bool { | ||
| if f == nil { | ||
| return true | ||
| } | ||
|
|
||
| if sourceTypeTag == "" { | ||
chombium marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return f.Mode == LogFilterModeExclude | ||
| } | ||
|
|
||
| prefix := ExtractPrefix(sourceTypeTag) | ||
| sourceType := LogSourceType(prefix) | ||
| if !sourceType.IsValid() { | ||
| return f.Mode == LogFilterModeExclude | ||
| } | ||
|
|
||
| inSet := f.Types.Contains(sourceType) | ||
| if f.Mode == LogFilterModeInclude { | ||
| return inSet | ||
| } | ||
| return !inSet | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.