apps/couchdb.go (145 lines of code) (raw):

// Copyright 2021 Google LLC // // 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 apps import ( "context" "fmt" "github.com/GoogleCloudPlatform/ops-agent/confgenerator" "github.com/GoogleCloudPlatform/ops-agent/confgenerator/fluentbit" "github.com/GoogleCloudPlatform/ops-agent/confgenerator/otel" "github.com/GoogleCloudPlatform/ops-agent/internal/secret" ) type MetricsReceiverCouchdb struct { confgenerator.ConfigComponent `yaml:",inline"` confgenerator.MetricsReceiverShared `yaml:",inline"` Endpoint string `yaml:"endpoint" validate:"omitempty,url,startswith=http:"` Username string `yaml:"username" validate:"required_with=Password"` Password secret.String `yaml:"password" validate:"required_with=Username"` } const defaultCouchdbEndpoint = "http://localhost:5984" func (MetricsReceiverCouchdb) Type() string { return "couchdb" } func (r MetricsReceiverCouchdb) Pipelines(_ context.Context) ([]otel.ReceiverPipeline, error) { if r.Endpoint == "" { r.Endpoint = defaultCouchdbEndpoint } return []otel.ReceiverPipeline{{ Receiver: otel.Component{ Type: "couchdb", Config: map[string]interface{}{ "collection_interval": r.CollectionIntervalString(), "endpoint": r.Endpoint, "username": r.Username, "password": r.Password.SecretValue(), }, }, Processors: map[string][]otel.Component{"metrics": { otel.NormalizeSums(), otel.MetricsTransform( otel.AddPrefix("workload.googleapis.com"), ), otel.ModifyInstrumentationScope(r.Type(), "1.0"), }}, }}, nil } func init() { confgenerator.MetricsReceiverTypes.RegisterType(func() confgenerator.MetricsReceiver { return &MetricsReceiverCouchdb{} }) } type LoggingProcessorCouchdb struct { confgenerator.ConfigComponent `yaml:",inline"` } func (LoggingProcessorCouchdb) Type() string { return "couchdb" } func (p LoggingProcessorCouchdb) Components(ctx context.Context, tag string, uid string) []fluentbit.Component { c := confgenerator.LoggingProcessorParseMultilineRegex{ LoggingProcessorParseRegexComplex: confgenerator.LoggingProcessorParseRegexComplex{ Parsers: []confgenerator.RegexParser{ { // Format https://github.com/apache/couchdb/blob/main/src/couch_log/src/couch_log_writer_syslog.erl#L72 // Sample line: [notice] 2021-12-02T23:36:42.555157Z nonode@nohost <0.17165.1> a5f585a0d3 localhost:5984 127.0.0.1 otelu PUT /oteld 201 ok 16 Regex: `^\[(?<level>\w*)\] (?<timestamp>[\d\-\.:TZ]+) (?<node>\S+)@(?<host>[^\s]+) \<(?<pid>[^ ]*)\> [\w-]+ (?<http_request_serverIp>[^ ]*) (?<http_request_remoteIp>[^ ]*) (?<message>(?<remote_user>[^ ]*) (?<http_request_requestMethod>[^ ]*) (?<path>[^ ]*) (?<http_request_status>[^ ]*) (?<status_message>[^ ]*) (?<http_request_responseSize>[\d]*)$)`, Parser: confgenerator.ParserShared{ TimeKey: "timestamp", TimeFormat: "%Y-%m-%dT%H:%M:%S.%L%z", Types: map[string]string{ "http_request_status": "integer", }, }, }, { /* Format https://github.com/apache/couchdb/blob/main/src/couch_log/src/couch_log_writer_syslog.erl#L72 Sample line1: [info] 2022-01-12T16:52:56.998128Z nonode@nohost <0.216.0> -------- Apache CouchDB has started. Time to relax. Sample line2: [error] 2022-01-12T16:53:03.094488Z nonode@nohost emulator -------- Error in process <0.463.0> with exit value: {database_does_not_exist,[{mem3_shards,load_shards_from_db,"_users",[{file,"src/mem3_shards.erl"},{line,399}]},{mem3_shards,load_shards_from_disk,1,[{file,"src/mem3_shards.erl"},{line,374}]},{mem3_shards,load_shards_from_disk,2,[{file,"src/mem3_shards.erl"},{line,403}]},{mem3_shards,for_docid,3,[{file,"src/mem3_shards.erl"},{line,96}]},{fabric_doc_open,go,3,[{file,"src/fabric_doc_open.erl"},{line,39}]},{chttpd_auth_cache,ensure_auth_ddoc_exists,2,[{file,"src/chttpd_auth_cache.erl"},{line,198}]},{chttpd_auth_cache,listen_for_changes,1,[{file,"src/chttpd_auth_cache.erl"},{line,145}]}]} */ Regex: `^\[(?<level>\w*)\] (?<timestamp>[\d\-\.:TZ]+) (?<node>\S+)@(?<host>[^\s]+) (?<message>[\s\S]*(\<(?<pid>[^>]+)\>)[\s\S]*)`, Parser: confgenerator.ParserShared{ TimeKey: "timestamp", TimeFormat: "%Y-%m-%dT%H:%M:%S.%L%z", }, }, }, }, }.Components(ctx, tag, uid) fields := map[string]*confgenerator.ModifyField{ "severity": { CopyFrom: "jsonPayload.level", MapValues: map[string]string{ "emerg": "EMERGENCY", "emergency": "EMERGENCY", "alert": "ALERT", "crit": "CRITICAL", "critical": "CRITICAL", "error": "ERROR", "err": "ERROR", "warn": "WARNING", "warning": "WARNING", "notice": "NOTICE", "info": "INFO", "debug": "DEBUG", }, MapValuesExclusive: true, }, InstrumentationSourceLabel: instrumentationSourceValue(p.Type()), } // Generate the httpRequest structure. for _, field := range []string{ "serverIp", "remoteIp", "requestMethod", "status", "responseSize", } { fields[fmt.Sprintf("httpRequest.%s", field)] = &confgenerator.ModifyField{ MoveFrom: fmt.Sprintf("jsonPayload.http_request_%s", field), } } // Log levels documented: https://docs.couchdb.org/en/stable/config/logging.html#log/level c = append(c, confgenerator.LoggingProcessorModifyFields{ Fields: fields, }.Components(ctx, tag, uid)..., ) return c } type LoggingReceiverCouchdb struct { LoggingProcessorCouchdb `yaml:",inline"` ReceiverMixin confgenerator.LoggingReceiverFilesMixin `yaml:",inline" validate:"structonly"` } func (r LoggingReceiverCouchdb) Components(ctx context.Context, tag string) []fluentbit.Component { if len(r.ReceiverMixin.IncludePaths) == 0 { r.ReceiverMixin.IncludePaths = []string{ // Default log file "/var/log/couchdb/couchdb.log", } } r.ReceiverMixin.MultilineRules = []confgenerator.MultilineRule{ { StateName: "start_state", NextState: "cont", Regex: `^\[\w+\]`, }, { StateName: "cont", NextState: "cont", Regex: `^(?!\[\w+\])`, }, } c := r.ReceiverMixin.Components(ctx, tag) c = append(c, r.LoggingProcessorCouchdb.Components(ctx, tag, "couchdb")...) return c } func init() { confgenerator.LoggingProcessorTypes.RegisterType(func() confgenerator.LoggingProcessor { return &LoggingProcessorCouchdb{} }) confgenerator.LoggingReceiverTypes.RegisterType(func() confgenerator.LoggingReceiver { return &LoggingReceiverCouchdb{} }) }