Skip to content
This repository has been archived by the owner on Jan 9, 2022. It is now read-only.

Commit

Permalink
add logpipe binary to .gitignore
Browse files Browse the repository at this point in the history
add writer.File and its tests and benchmarks
closes #10
  • Loading branch information
arsham committed Oct 8, 2017
1 parent d18ac88 commit 93c61ef
Show file tree
Hide file tree
Showing 9 changed files with 528 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Binaries for programs and plugins
logpipe
*.exe
*.dll
*.so
Expand Down
74 changes: 71 additions & 3 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions internal/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2016 Arsham Shirvani <arshamshirvani@gmail.com>. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license
// License that can be found in the LICENSE file.

// Package internal contains some internal functionalities needed for piplog.
// Other commonly used logic are places in the internal package itself. There
// is logging set up.
package internal
69 changes: 69 additions & 0 deletions internal/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2017 Arsham Shirvani <arshamshirvani@gmail.com>. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license
// License that can be found in the LICENSE file.

package internal

import (
"io/ioutil"
"strings"

"github.com/Sirupsen/logrus"
)

// FieldLogger interface set by logrus
type FieldLogger logrus.FieldLogger

// Level type set by logrus
type Level logrus.Level

// Logger embeds logrus.Logger
type Logger struct{ *logrus.Logger }

// Entry embeds logrus.Entry
type Entry struct{ *logrus.Entry }

// StandardLogger returns an instance of Logger
func StandardLogger() *Logger { return &Logger{logrus.StandardLogger()} }

const (
// InfoLevel for Info level
InfoLevel = logrus.InfoLevel
// WarnLevel for Warn level
WarnLevel = logrus.WarnLevel
// DebugLevel for Debug level
DebugLevel = logrus.DebugLevel
// ErrorLevel for Error level
ErrorLevel = logrus.ErrorLevel
)

// Get returns the default logger with the given log level.
func Get(level string) *Logger {
logrus.SetLevel(ErrorLevel)
customFormatter := new(logrus.TextFormatter)
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
logrus.SetFormatter(customFormatter)
customFormatter.FullTimestamp = true
switch strings.ToLower(level) {
case "debug":
logrus.SetLevel(DebugLevel)
case "info":
logrus.SetLevel(InfoLevel)
case "warn":
logrus.SetLevel(WarnLevel)
case "error":
logrus.SetLevel(ErrorLevel)
default:
logrus.SetLevel(ErrorLevel)
}

return StandardLogger()
}

// DiscardLogger returns a dummy logger.
// This is useful for tests when you don't want to actually write to the Stdout.
func DiscardLogger() *Logger {
log := logrus.New()
log.Out = ioutil.Discard
return &Logger{log}
}
53 changes: 53 additions & 0 deletions internal/log_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2016 Arsham Shirvani <arshamshirvani@gmail.com>. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license
// License that can be found in the LICENSE file.

package internal_test

import (
"fmt"
"io/ioutil"
"testing"

"github.com/arsham/logpipe/internal"
)

func TestGetLoggerLevels(t *testing.T) {
t.Parallel()
tcs := []struct {
level string
expected internal.Level
}{
{"debug", internal.Level(internal.DebugLevel)},
{"info", internal.Level(internal.InfoLevel)},
{"warn", internal.Level(internal.WarnLevel)},
{"error", internal.Level(internal.ErrorLevel)},
{"DEBUG", internal.Level(internal.DebugLevel)},
{"INFO", internal.Level(internal.InfoLevel)},
{"WARN", internal.Level(internal.WarnLevel)},
{"ERROR", internal.Level(internal.ErrorLevel)},
{"dEbUG", internal.Level(internal.DebugLevel)},
{"iNfO", internal.Level(internal.InfoLevel)},
{"wArN", internal.Level(internal.WarnLevel)},
{"eRrOR", internal.Level(internal.ErrorLevel)},
{"", internal.Level(internal.ErrorLevel)},
{"sdfsdf", internal.Level(internal.ErrorLevel)},
}

for i, tc := range tcs {
name := fmt.Sprintf("case_%d", i)
t.Run(name, func(t *testing.T) {
logger := internal.Get(tc.level)
if internal.Level(logger.Level) != tc.expected {
t.Errorf("want (%v), got (%v)", tc.expected, logger.Level)
}
})
}
}

func TestGetDiscardLogger(t *testing.T) {
logger := internal.DiscardLogger()
if logger.Out != ioutil.Discard {
t.Errorf("want (ioutil.Discard), got (%v)", logger.Out)
}
}
13 changes: 13 additions & 0 deletions logpipe_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"testing"
)

func TestLogpipe(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Logpipe Suite")
}
95 changes: 95 additions & 0 deletions writer/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2017 Arsham Shirvani <arshamshirvani@gmail.com>. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license
// License that can be found in the LICENSE file.

package writer

import (
"bufio"
"os"
"sync"
"time"

"github.com/pkg/errors"
)

// File writs records log entries to a file. It buffers the writes to obtain
// better performance. It flushes the buffer every 1 seconds.
// It implements io.WriteCloser interface.
type File struct {
name string
file *os.File
sync.Mutex // guards against the buffer
buf *bufio.Writer
}

// NewFile returns error if the file can not be created.
func NewFile(location string) (*File, error) {
var (
f *os.File
err error
)

if f, err = os.OpenFile(location, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err != nil {
if os.IsPermission(err) {
return nil, errors.Wrap(err, "opening file")
}
}
buf := bufio.NewWriter(f)

fl := &File{
file: f,
name: location,
buf: buf,
}

// this goroutine will flush the logs onto the file
go func(buf *bufio.Writer) {
ticker := time.NewTicker(time.Second)
for range ticker.C {
fl.Lock()
buf.Flush()
fl.Unlock()
}
}(buf)

return fl, nil
}

// Close closes the File.
func (f *File) Close() error {
if err := f.Flush(); err != nil {
return errors.Wrap(err, "flushing on close")
}
return f.file.Close()
}

// Name returns the file location on disk.
func (f *File) Name() string {
return f.name
}

// Write writes the input bytes to the file.
// The write will not appear in the file unless the buffer is flushed. (see Flush())
func (f *File) Write(p []byte) (int, error) {
f.Lock()
defer f.Unlock()

n1, err := f.buf.Write(p)
if err != nil {
return n1, errors.Wrap(err, "writing the bytes")
}

n2, err := f.buf.Write([]byte("\n")) // required for creating a new line
if err != nil {
return n2, errors.Wrap(err, "writing new line")
}
return n1, nil
}

// Flush flushes the underlying buffer.
func (f *File) Flush() error {
f.Lock()
defer f.Unlock()
return f.buf.Flush()
}
Loading

0 comments on commit 93c61ef

Please sign in to comment.