diff --git a/benchmark_test.go b/benchmark_test.go
index acf611c..be88bfd 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -9,8 +9,7 @@ import (
 )
 
 func BenchmarkNoField(b *testing.B) {
-	logger := logf.New()
-	logger.SetWriter(io.Discard)
+	logger := logf.New(io.Discard)
 	b.ReportAllocs()
 	b.ResetTimer()
 	b.RunParallel(func(p *testing.PB) {
@@ -21,8 +20,7 @@ func BenchmarkNoField(b *testing.B) {
 }
 
 func BenchmarkOneField(b *testing.B) {
-	logger := logf.New()
-	logger.SetWriter(io.Discard)
+	logger := logf.New(io.Discard)
 	b.ReportAllocs()
 	b.ResetTimer()
 	b.RunParallel(func(p *testing.PB) {
@@ -33,8 +31,7 @@ func BenchmarkOneField(b *testing.B) {
 }
 
 func BenchmarkThreeFields(b *testing.B) {
-	logger := logf.New()
-	logger.SetWriter(io.Discard)
+	logger := logf.New(io.Discard)
 	b.ReportAllocs()
 	b.ResetTimer()
 
@@ -50,8 +47,7 @@ func BenchmarkThreeFields(b *testing.B) {
 }
 
 func BenchmarkErrorField(b *testing.B) {
-	logger := logf.New()
-	logger.SetWriter(io.Discard)
+	logger := logf.New(io.Discard)
 	b.ReportAllocs()
 	b.ResetTimer()
 
@@ -65,8 +61,7 @@ func BenchmarkErrorField(b *testing.B) {
 }
 
 func BenchmarkHugePayload(b *testing.B) {
-	logger := logf.New()
-	logger.SetWriter(io.Discard)
+	logger := logf.New(io.Discard)
 	b.ReportAllocs()
 	b.ResetTimer()
 
@@ -89,8 +84,7 @@ func BenchmarkHugePayload(b *testing.B) {
 }
 
 func BenchmarkThreeFields_WithCaller(b *testing.B) {
-	logger := logf.New()
-	logger.SetWriter(io.Discard)
+	logger := logf.New(io.Discard)
 	logger.SetCallerFrame(true, 3)
 	b.ReportAllocs()
 	b.ResetTimer()
@@ -107,8 +101,7 @@ func BenchmarkThreeFields_WithCaller(b *testing.B) {
 }
 
 func BenchmarkNoField_WithColor(b *testing.B) {
-	logger := logf.New()
-	logger.SetWriter(io.Discard)
+	logger := logf.New(io.Discard)
 	logger.SetColorOutput(true)
 	b.ReportAllocs()
 	b.ResetTimer()
@@ -121,8 +114,7 @@ func BenchmarkNoField_WithColor(b *testing.B) {
 }
 
 func BenchmarkOneField_WithColor(b *testing.B) {
-	logger := logf.New()
-	logger.SetWriter(io.Discard)
+	logger := logf.New(io.Discard)
 	logger.SetColorOutput(true)
 	b.ReportAllocs()
 	b.ResetTimer()
@@ -134,8 +126,7 @@ func BenchmarkOneField_WithColor(b *testing.B) {
 }
 
 func BenchmarkThreeFields_WithColor(b *testing.B) {
-	logger := logf.New()
-	logger.SetWriter(io.Discard)
+	logger := logf.New(io.Discard)
 	logger.SetColorOutput(true)
 	b.ReportAllocs()
 	b.ResetTimer()
@@ -152,8 +143,7 @@ func BenchmarkThreeFields_WithColor(b *testing.B) {
 }
 
 func BenchmarkErrorField_WithColor(b *testing.B) {
-	logger := logf.New()
-	logger.SetWriter(io.Discard)
+	logger := logf.New(io.Discard)
 	logger.SetColorOutput(true)
 	b.ReportAllocs()
 	b.ResetTimer()
@@ -168,8 +158,7 @@ func BenchmarkErrorField_WithColor(b *testing.B) {
 }
 
 func BenchmarkHugePayload_WithColor(b *testing.B) {
-	logger := logf.New()
-	logger.SetWriter(io.Discard)
+	logger := logf.New(io.Discard)
 	logger.SetColorOutput(true)
 	b.ReportAllocs()
 	b.ResetTimer()
diff --git a/examples/main.go b/examples/main.go
index bf7c2fe..9575fe2 100644
--- a/examples/main.go
+++ b/examples/main.go
@@ -2,13 +2,14 @@ package main
 
 import (
 	"errors"
+	"os"
 	"time"
 
 	"github.com/zerodha/logf"
 )
 
 func main() {
-	logger := logf.New()
+	logger := logf.New(os.Stderr)
 
 	// Basic log.
 	logger.Info("starting app")
diff --git a/field_logger.go b/field_logger.go
index 7a3fcb6..30b08d3 100644
--- a/field_logger.go
+++ b/field_logger.go
@@ -4,31 +4,32 @@ import "os"
 
 type FieldLogger struct {
 	fields Fields
-	logger *Logger
+	logger Logger
 }
 
-func (l *FieldLogger) Debug(msg string) {
+// Debug emits a debug log line.
+func (l FieldLogger) Debug(msg string) {
 	l.logger.handleLog(msg, DebugLevel, l.fields)
 }
 
 // Info emits a info log line.
-func (l *FieldLogger) Info(msg string) {
+func (l FieldLogger) Info(msg string) {
 	l.logger.handleLog(msg, InfoLevel, l.fields)
 }
 
 // Warn emits a warning log line.
-func (l *FieldLogger) Warn(msg string) {
+func (l FieldLogger) Warn(msg string) {
 	l.logger.handleLog(msg, WarnLevel, l.fields)
 }
 
 // Error emits an error log line.
-func (l *FieldLogger) Error(msg string) {
+func (l FieldLogger) Error(msg string) {
 	l.logger.handleLog(msg, ErrorLevel, l.fields)
 }
 
 // Fatal emits a fatal level log line.
 // It aborts the current program with an exit code of 1.
-func (l *FieldLogger) Fatal(msg string) {
+func (l FieldLogger) Fatal(msg string) {
 	l.logger.handleLog(msg, ErrorLevel, l.fields)
 	os.Exit(1)
 }
diff --git a/log.go b/log.go
index e749535..6235e21 100644
--- a/log.go
+++ b/log.go
@@ -26,25 +26,12 @@ var (
 // Logger is the interface for all log operations
 // related to emitting logs.
 type Logger struct {
-	mu                   sync.Mutex // Atomic writes.
-	out                  io.Writer  // Output destination.
-	level                Level      // Verbosity of logs.
-	tsFormat             string     // Timestamp format.
-	enableColor          bool       // Colored output.
-	enableCaller         bool       // Print caller information.
-	callerSkipFrameCount int        // Number of frames to skip when detecting caller
-}
-
-// Opts represents various properties
-// to configure logger.
-type Opts struct {
-	Writer          io.Writer
-	Lvl             Level
-	TimestampFormat string
-	EnableColor     bool
-	EnableCaller    bool
-	// CallerSkipFrameCount is the count of the number of frames to skip when computing the file name and line number
-	CallerSkipFrameCount int
+	out                  io.Writer // Output destination.
+	level                Level     // Verbosity of logs.
+	tsFormat             string    // Timestamp format.
+	enableColor          bool      // Colored output.
+	enableCaller         bool      // Print caller information.
+	callerSkipFrameCount int       // Number of frames to skip when detecting caller
 }
 
 // Fields is a map of arbitrary KV pairs
@@ -82,10 +69,14 @@ var colorLvlMap = [...]string{
 
 // New instantiates a logger object.
 // It writes to `stderr` as the default and it's non configurable.
-func New() *Logger {
+func New(out io.Writer) Logger {
 	// Initialise logger with sane defaults.
-	return &Logger{
-		out:                  os.Stderr,
+	if out == nil {
+		out = os.Stderr
+	}
+
+	return Logger{
+		out:                  newSyncWriter(out),
 		level:                InfoLevel,
 		tsFormat:             defaultTSFormat,
 		enableColor:          false,
@@ -94,6 +85,31 @@ func New() *Logger {
 	}
 }
 
+// syncWriter is a wrapper around io.Writer that
+// synchronizes writes using a mutex.
+type syncWriter struct {
+	sync.Mutex
+	w io.Writer
+}
+
+// Write synchronously to the underlying io.Writer.
+func (w *syncWriter) Write(p []byte) (int, error) {
+	w.Lock()
+	n, err := w.w.Write(p)
+	w.Unlock()
+	return n, err
+}
+
+// newSyncWriter wraps an io.Writer with syncWriter. It can
+// be used as an io.Writer as syncWriter satisfies the io.Writer interface.
+func newSyncWriter(in io.Writer) *syncWriter {
+	if in == nil {
+		return &syncWriter{w: os.Stderr}
+	}
+
+	return &syncWriter{w: in}
+}
+
 // String representation of the log severity.
 func (l Level) String() string {
 	switch l {
@@ -114,73 +130,75 @@ func (l Level) String() string {
 
 // SetLevel sets the verbosity for logger.
 // Verbosity can be dynamically changed by the caller.
-func (l *Logger) SetLevel(lvl Level) {
+func (l Logger) SetLevel(lvl Level) Logger {
 	l.level = lvl
+	return l
 }
 
 // SetWriter sets the output writer for the logger
-func (l *Logger) SetWriter(w io.Writer) {
-	l.mu.Lock()
-	l.out = w
-	l.mu.Unlock()
+func (l Logger) SetWriter(w io.Writer) Logger {
+	l.out = &syncWriter{w: w}
+	return l
 }
 
 // SetTimestampFormat sets the timestamp format for the `timestamp` key.
-func (l *Logger) SetTimestampFormat(f string) {
+func (l Logger) SetTimestampFormat(f string) Logger {
 	l.tsFormat = f
+	return l
 }
 
 // SetColorOutput enables/disables colored output.
-func (l *Logger) SetColorOutput(color bool) {
+func (l Logger) SetColorOutput(color bool) Logger {
 	l.enableColor = color
+	return l
 }
 
 // SetCallerFrame enables/disables the caller source in the log line.
-func (l *Logger) SetCallerFrame(caller bool, depth int) {
+func (l Logger) SetCallerFrame(caller bool, depth int) Logger {
 	l.enableCaller = caller
 	l.callerSkipFrameCount = depth
+	return l
 }
 
 // Debug emits a debug log line.
-func (l *Logger) Debug(msg string) {
+func (l Logger) Debug(msg string) {
 	l.handleLog(msg, DebugLevel, nil)
 }
 
 // Info emits a info log line.
-func (l *Logger) Info(msg string) {
+func (l Logger) Info(msg string) {
 	l.handleLog(msg, InfoLevel, nil)
 }
 
 // Warn emits a warning log line.
-func (l *Logger) Warn(msg string) {
+func (l Logger) Warn(msg string) {
 	l.handleLog(msg, WarnLevel, nil)
 }
 
 // Error emits an error log line.
-func (l *Logger) Error(msg string) {
+func (l Logger) Error(msg string) {
 	l.handleLog(msg, ErrorLevel, nil)
 }
 
 // Fatal emits a fatal level log line.
 // It aborts the current program with an exit code of 1.
-func (l *Logger) Fatal(msg string) {
+func (l Logger) Fatal(msg string) {
 	l.handleLog(msg, FatalLevel, nil)
 	os.Exit(1)
 }
 
 // WithFields returns a new entry with `fields` set.
-func (l *Logger) WithFields(fields Fields) *FieldLogger {
-	fl := &FieldLogger{
+func (l Logger) WithFields(fields Fields) FieldLogger {
+	return FieldLogger{
 		fields: fields,
 		logger: l,
 	}
-	return fl
 }
 
 // WithError returns a Logger with the "error" key set to `err`.
-func (l *Logger) WithError(err error) *FieldLogger {
+func (l Logger) WithError(err error) FieldLogger {
 	if err == nil {
-		return &FieldLogger{logger: l}
+		return FieldLogger{logger: l}
 	}
 
 	return l.WithFields(Fields{
@@ -190,7 +208,7 @@ func (l *Logger) WithError(err error) *FieldLogger {
 
 // handleLog emits the log after filtering log level
 // and applying formatting of the fields.
-func (l *Logger) handleLog(msg string, lvl Level, fields Fields) {
+func (l Logger) handleLog(msg string, lvl Level, fields Fields) {
 	// Discard the log if the verbosity is higher.
 	// For eg, if the lvl is `3` (error), but the incoming message is `0` (debug), skip it.
 	if lvl < l.level {
@@ -221,13 +239,11 @@ func (l *Logger) handleLog(msg string, lvl Level, fields Fields) {
 	}
 	buf.AppendString("\n")
 
-	l.mu.Lock()
 	_, err := l.out.Write(buf.Bytes())
 	if err != nil {
 		// Should ideally never happen.
 		stdlog.Printf("error logging: %v", err)
 	}
-	l.mu.Unlock()
 
 	buf.Reset()
 
diff --git a/log_test.go b/log_test.go
index ea3999d..29c8979 100644
--- a/log_test.go
+++ b/log_test.go
@@ -3,6 +3,7 @@ package logf
 import (
 	"bytes"
 	"errors"
+	"os"
 	"strconv"
 	"sync"
 	"testing"
@@ -37,7 +38,7 @@ func TestLevelParsing(t *testing.T) {
 }
 
 func TestNewLoggerDefault(t *testing.T) {
-	l := New()
+	l := New(os.Stderr)
 	assert.Equal(t, l.level, InfoLevel, "level is info")
 	assert.Equal(t, l.enableColor, false, "color output is disabled")
 	assert.Equal(t, l.enableCaller, false, "caller is disabled")
@@ -47,8 +48,7 @@ func TestNewLoggerDefault(t *testing.T) {
 
 func TestLogFormat(t *testing.T) {
 	buf := &bytes.Buffer{}
-	l := New()
-	l.SetWriter(buf)
+	l := New(buf)
 	l.SetColorOutput(false)
 
 	// Info log.
@@ -79,8 +79,7 @@ func TestLogFormat(t *testing.T) {
 // These test are typically meant to be run with the data race detector.
 func TestLoggerConcurrency(t *testing.T) {
 	buf := &bytes.Buffer{}
-	l := New()
-	l.SetWriter(buf)
+	l := New(buf)
 	l.SetColorOutput(false)
 
 	for _, n := range []int{10, 100, 1000} {
@@ -93,7 +92,7 @@ func TestLoggerConcurrency(t *testing.T) {
 	}
 }
 
-func genLogs(l *Logger) {
+func genLogs(l Logger) {
 	for i := 0; i < 100; i++ {
 		l.WithFields(Fields{"index": strconv.FormatInt(int64(i), 10)}).Info("random log")
 	}