Skip to content

Commit 734a5ef

Browse files
authored
Logging (#22)
using logrus for logging. Initial commit for using logrus for logging. Two different types of logs are persisted. 1. task ranker logs pertaining to the library. Uses text formatter. 2. task ranking results logs pertaining to the results of executing the task ranking strategy. Uses json formatter. Implemented hooks that wrap around io.Writer that persist the log to the configured file if the provided topics are found as keys in the fields.
1 parent 2037da6 commit 734a5ef

File tree

9 files changed

+274
-4
lines changed

9 files changed

+274
-4
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,13 @@ HOST = localhost
148148

149149
#### Tear-Down
150150
Once finished testing, tear down the test environment by running [`./tear_down_test_env`](./tear_down_test_env).
151+
152+
### Logs
153+
Task Ranker uses [logrus](https://github.com/sirupsen/logrus) for logging. To prevent logs from Task Ranker
154+
mixing in with logs from the application that is using it, console logging is disabled.
155+
There are [two types of logs](./logger/logger.go) as mentioned below.
156+
1. Task Ranker logs - These logs are Task Ranker specific and correspond to functioning of the library.
157+
These logs are written to a file named **_task\_ranker\_logs\_\<timestamp\>.log_**.
158+
2. Task Ranking Results logs - These are the results of task ranking using one of task ranking strategies.
159+
These logs are written to a file named **_task\_ranking\_results\_\<timestamp\>.log_**. To simplify parsing
160+
these logs are written in JSON format.

datafetcher/prometheus/dataFetcher.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import (
1818
"context"
1919
"github.com/pkg/errors"
2020
"github.com/pradykaushik/task-ranker/datafetcher"
21+
"github.com/pradykaushik/task-ranker/logger"
2122
"github.com/pradykaushik/task-ranker/query"
2223
"github.com/pradykaushik/task-ranker/strategies"
2324
"github.com/prometheus/client_golang/api"
2425
"github.com/prometheus/client_golang/api/prometheus/v1"
2526
"github.com/prometheus/common/model"
27+
"github.com/sirupsen/logrus"
2628
"net/http"
2729
"time"
2830
)
@@ -94,13 +96,20 @@ func (f *DataFetcher) Fetch() (result model.Value, err error) {
9496
var client api.Client
9597
client, err = api.NewClient(api.Config{Address: f.endpoint})
9698
if err != nil {
97-
err = errors.Wrap(err, "failed to fetch data from prometheus")
99+
err = errors.Wrap(err, "failed to create prometheus client")
98100
return
99101
}
100102
v1Api := v1.NewAPI(client)
101103
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
102104
defer cancel()
103105
// TODO do not ignore warnings. maybe log them?
104106
result, _, err = v1Api.Query(ctx, queryString, time.Now())
107+
if err == nil {
108+
logger.WithFields(logrus.Fields{
109+
"stage": "data-fetcher",
110+
"query": queryString,
111+
"query_result": result,
112+
}).Log(logrus.InfoLevel, "data fetched")
113+
}
105114
return
106115
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ require (
99
github.com/prometheus/client_golang v1.6.0
1010
github.com/prometheus/common v0.9.1
1111
github.com/robfig/cron/v3 v3.0.0
12+
github.com/sirupsen/logrus v1.6.0
1213
github.com/stretchr/testify v1.6.0
1314
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGn
3333
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
3434
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
3535
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
36+
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
3637
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
3738
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
3839
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -70,6 +71,8 @@ github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
7071
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
7172
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
7273
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
74+
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
75+
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
7376
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
7477
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
7578
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -89,6 +92,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
8992
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
9093
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
9194
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
95+
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
9296
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
9397
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
9498
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

logger/hook.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2020 Pradyumna Kaushik
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package logger
16+
17+
import (
18+
"github.com/pkg/errors"
19+
"github.com/sirupsen/logrus"
20+
"io"
21+
)
22+
23+
// WriterHook is a hook that writes logs, that contain at least one of the specified set of topics
24+
// as keys in its fields, to the specified Writer. The logs are formatted using the specified formatter.
25+
type WriterHook struct {
26+
formatter logrus.Formatter
27+
writer io.Writer
28+
topics map[string]struct{}
29+
}
30+
31+
// newWriterHook instantiates and returns a new WriterHook.
32+
func newWriterHook(formatter logrus.Formatter, writer io.Writer, topics ...string) logrus.Hook {
33+
hook := &WriterHook{
34+
formatter: formatter,
35+
writer: writer,
36+
topics: make(map[string]struct{}),
37+
}
38+
for _, topic := range topics {
39+
hook.topics[topic] = struct{}{}
40+
}
41+
42+
return hook
43+
}
44+
45+
// Levels return the list of levels for which this hook will be fired.
46+
func (h WriterHook) Levels() []logrus.Level {
47+
// We do not want debug and trace level logs to be persisted as they are typically temporary.
48+
return []logrus.Level{
49+
logrus.PanicLevel,
50+
logrus.FatalLevel,
51+
logrus.ErrorLevel,
52+
logrus.WarnLevel,
53+
logrus.InfoLevel,
54+
}
55+
}
56+
57+
// Fire checks whether the fields in the provided entry contain at least one of the specified
58+
// topics and if yes, formats the entry using the specified formatter and then writes it to the
59+
// specified Writer.
60+
func (h *WriterHook) Fire(entry *logrus.Entry) error {
61+
// Logging only if any of the provided topics are found as keys in fields.
62+
allow := false
63+
for topic := range h.topics {
64+
if _, ok := entry.Data[topic]; ok {
65+
allow = true
66+
break
67+
}
68+
}
69+
70+
var err error
71+
var formattedLog []byte
72+
if allow {
73+
formattedLog, err = h.formatter.Format(entry)
74+
if err != nil {
75+
err = errors.Wrap(err, "failed to format entry")
76+
} else {
77+
_, err = h.writer.Write(formattedLog)
78+
}
79+
}
80+
return err
81+
}

logger/logger.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2020 Pradyumna Kaushik
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package logger
16+
17+
import (
18+
"fmt"
19+
"github.com/pkg/errors"
20+
"github.com/sirupsen/logrus"
21+
"io/ioutil"
22+
"os"
23+
"time"
24+
)
25+
26+
const (
27+
// Using RFC3339Nano as the timestamp format for logs as prometheus scrape interval can be in milliseconds.
28+
timestampFormat = time.RFC3339Nano
29+
// Prefix of the name of the log file to store task ranker logs.
30+
// This will be suffixed with the timestamp, associated with creating the file, to obtain the log filename.
31+
taskRankerLogFilePrefix = "task_ranker_logs"
32+
// Prefix of the name of the log file to store task ranking results.
33+
// This will be suffixed with the timestamp, associated with creating the file, to obtain the log filename.
34+
taskRankingResultsLogFilePrefix = "task_ranking_results"
35+
// Giving everyone read and write permissions to the log files.
36+
logFilePermissions = 0666
37+
)
38+
39+
// instantiating a Logger to be used. This instance is configured and maintained locally.
40+
var log = logrus.New()
41+
42+
var taskRankerLogFile *os.File
43+
var taskRankingResultsLogFile *os.File
44+
45+
// createTaskRankerLogFile creates the log file to which task ranker logs are persisted.
46+
func createTaskRankerLogFile(now time.Time) error {
47+
var err error
48+
filename := fmt.Sprintf("%s_%v.log", taskRankerLogFilePrefix, now.UnixNano())
49+
taskRankerLogFile, err = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, logFilePermissions)
50+
if err != nil {
51+
err = errors.Wrap(err, "failed to create task ranker operations log file")
52+
}
53+
return err
54+
}
55+
56+
// createTaskRankingResultsLogFile creates the log file to which task ranking results are persisted.
57+
func createTaskRankingResultsLogFile(now time.Time) error {
58+
var err error
59+
filename := fmt.Sprintf("%s_%v.log", taskRankingResultsLogFilePrefix, now.UnixNano())
60+
taskRankingResultsLogFile, err = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, logFilePermissions)
61+
if err != nil {
62+
err = errors.Wrap(err, "failed to create task ranker log file")
63+
}
64+
return err
65+
}
66+
67+
// Configure the logger. To be prevented task ranker logs from mixing with the logs of the application
68+
// that is using it, logging to the console is disabled and instead hooks that redirect logs to corresponding
69+
// log files are attached to the logger.
70+
func Configure() error {
71+
// Disabling log to stdout.
72+
log.SetOutput(ioutil.Discard)
73+
// Setting highest log level.
74+
log.SetLevel(logrus.InfoLevel)
75+
76+
// Creating the log files.
77+
now := time.Now()
78+
var err error
79+
80+
if err = createTaskRankerLogFile(now); err != nil {
81+
return err
82+
}
83+
if err = createTaskRankingResultsLogFile(now); err != nil {
84+
return err
85+
}
86+
87+
// Instantiate the hooks.
88+
jsonFormatter := &logrus.JSONFormatter{
89+
DisableHTMLEscape: true,
90+
TimestampFormat: timestampFormat,
91+
}
92+
93+
textFormatter := &logrus.TextFormatter{
94+
DisableColors: true,
95+
FullTimestamp: true,
96+
TimestampFormat: timestampFormat,
97+
}
98+
99+
log.AddHook(newWriterHook(textFormatter, taskRankerLogFile, "stage", "query", "query_result"))
100+
log.AddHook(newWriterHook(jsonFormatter, taskRankingResultsLogFile, "task_ranking_results"))
101+
102+
return nil
103+
}
104+
105+
func Done() error {
106+
var err error
107+
if taskRankerLogFile != nil {
108+
err = taskRankerLogFile.Close()
109+
if err != nil {
110+
err = errors.Wrap(err, "failed to close task ranker log file")
111+
}
112+
}
113+
114+
if taskRankingResultsLogFile != nil {
115+
err = taskRankingResultsLogFile.Close()
116+
if err != nil {
117+
err = errors.Wrap(err, "failed to close tank ranking results log file")
118+
}
119+
}
120+
return err
121+
}
122+
123+
// Aliasing logrus functions.
124+
var WithField = log.WithField
125+
var WithFields = log.WithFields
126+
var Info = log.Info
127+
var Infof = log.Infof
128+
var Error = log.Error
129+
var Errorf = log.Errorf
130+
var Warn = log.Warn
131+
var Warnf = log.Warnf

ranker.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ import (
1818
"fmt"
1919
"github.com/pkg/errors"
2020
df "github.com/pradykaushik/task-ranker/datafetcher"
21+
"github.com/pradykaushik/task-ranker/logger"
2122
"github.com/pradykaushik/task-ranker/query"
2223
"github.com/pradykaushik/task-ranker/strategies"
2324
"github.com/pradykaushik/task-ranker/strategies/factory"
2425
"github.com/pradykaushik/task-ranker/util"
2526
"github.com/robfig/cron/v3"
26-
"log"
27+
"github.com/sirupsen/logrus"
2728
"time"
2829
)
2930

@@ -73,7 +74,16 @@ func New(options ...Option) (*TaskRanker, error) {
7374
// Providing the prometheus scrape interval to the strategy.
7475
tRanker.Strategy.SetPrometheusScrapeInterval(tRanker.prometheusScrapeInterval)
7576
tRanker.termCh = util.NewSignalChannel()
76-
return tRanker, nil
77+
78+
// Configuring logger.
79+
err := logger.Configure()
80+
if err != nil {
81+
err = errors.Wrap(err, "failed to configure logger")
82+
if err = logger.Done(); err != nil {
83+
err = errors.Wrap(err, "failed to shutdown logger")
84+
}
85+
}
86+
return tRanker, err
7787
}
7888

7989
type Option func(*TaskRanker) error
@@ -163,6 +173,9 @@ func WithSchedule(specString string) Option {
163173
}
164174

165175
func (tRanker *TaskRanker) Start() {
176+
logger.WithFields(logrus.Fields{
177+
"stage": "task-ranker",
178+
}).Log(logrus.InfoLevel, "starting task ranker cron job")
166179
tRanker.runner = cron.New(cron.WithSeconds())
167180
tRanker.runner.Schedule(tRanker.Schedule, tRanker)
168181
tRanker.runner.Start()
@@ -174,13 +187,22 @@ func (tRanker *TaskRanker) Run() {
174187
}
175188
result, err := tRanker.DataFetcher.Fetch()
176189
if err != nil {
177-
log.Println(err.Error())
190+
logger.WithFields(logrus.Fields{
191+
"stage": "data-fetcher",
192+
}).Log(logrus.ErrorLevel, err.Error())
178193
} else {
179194
tRanker.Strategy.Execute(result)
180195
}
181196
}
182197

183198
func (tRanker *TaskRanker) Stop() {
199+
logger.WithFields(logrus.Fields{
200+
"stage": "task-ranker",
201+
}).Log(logrus.InfoLevel, "stopping task ranker cron job")
184202
tRanker.termCh.Close()
185203
tRanker.runner.Stop()
204+
err := logger.Done()
205+
if err != nil {
206+
fmt.Printf("failed to shutdown logger: %v", err)
207+
}
186208
}

strategies/taskRankCpuSharesStrategy.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ package strategies
1717
import (
1818
"github.com/pkg/errors"
1919
"github.com/pradykaushik/task-ranker/entities"
20+
"github.com/pradykaushik/task-ranker/logger"
2021
"github.com/pradykaushik/task-ranker/query"
2122
"github.com/prometheus/common/model"
23+
"github.com/sirupsen/logrus"
2224
"sort"
2325
"time"
2426
)
@@ -120,6 +122,10 @@ func (s *TaskRankCpuSharesStrategy) Execute(data model.Value) {
120122
}
121123

122124
// Submitting the ranked tasks to the receiver.
125+
logger.WithFields(logrus.Fields{
126+
"task_ranking_strategy": "cpushares",
127+
"task_ranking_results": tasks,
128+
}).Log(logrus.InfoLevel, "strategy executed")
123129
s.receiver.Receive(tasks)
124130
}
125131

0 commit comments

Comments
 (0)