<commit_before>package database

import (
	"fmt"
	"strings"
	"time"

	"github.com/getfider/fider/app/pkg/errors"
	"github.com/getfider/fider/app/pkg/log/console"

	"github.com/getfider/fider/app/pkg/dbx"
	"github.com/getfider/fider/app/pkg/env"
	"github.com/getfider/fider/app/pkg/log"
)

// Logger writes logs to a SQL database
type Logger struct {
	db      *dbx.Database
	console log.Logger
	level   log.Level
	tag     string
	props   log.Props
}

// NewLogger creates a new Logger
func NewLogger(tag string, db *dbx.Database) *Logger {
	level := strings.ToUpper(env.GetEnvOrDefault("LOG_LEVEL", ""))
	return &Logger{
		tag:     tag,
		db:      db,
		console: console.NewLogger(tag),
		level:   log.ParseLevel(level),
		props:   make(log.Props, 0),
	}
}

// SetLevel increases/decreases current log level
func (l *Logger) SetLevel(level log.Level) {
	l.level = level
	l.console.SetLevel(level)
}

// IsEnabled returns true if given level is enabled
func (l *Logger) IsEnabled(level log.Level) bool {
	return level >= l.level
}

// Debug logs a DEBUG message
func (l *Logger) Debug(message string) {
	l.console.Debug(message)
	l.log(log.DEBUG, message, nil)
}

// Debugf logs a DEBUG message
func (l *Logger) Debugf(message string, props log.Props) {
	l.console.Debugf(message, props)
	l.log(log.DEBUG, message, props)
}

// Info logs a INFO message
func (l *Logger) Info(message string) {
	l.console.Info(message)
	l.log(log.INFO, message, nil)
}

// Infof logs a INFO message
func (l *Logger) Infof(message string, props log.Props) {
	l.console.Infof(message, props)
	l.log(log.INFO, message, props)
}

// Warn logs a WARN message
func (l *Logger) Warn(message string) {
	l.console.Warn(message)
	l.log(log.WARN, message, nil)
}

// Warnf logs a WARN message
func (l *Logger) Warnf(message string, props log.Props) {
	l.console.Warnf(message, props)
	l.log(log.WARN, message, props)
}

// Errorf logs a ERROR message
func (l *Logger) Errorf(message string, props log.Props) {
	l.console.Errorf(message, props)
	l.log(log.ERROR, message, props)
}

// Error logs a ERROR message
func (l *Logger) Error(err error) {
	if err != nil {
		l.log(log.ERROR, err.Error(), nil)
	} else {
		l.log(log.ERROR, "nil", nil)
	}
	l.console.Error(err)
}

// Write writes len(p) bytes from p to the underlying data stream.
func (l *Logger) Write(p []byte) (int, error) {
	l.console.Write(p)
	l.Debug(fmt.Sprintf("%s", p))
	return len(p), nil
}

// New returns a copy of current logger with empty context
func (l *Logger) New() log.Logger {
	return NewLogger(l.tag, l.db)
}

// SetProperty with given key:value into current logger context
func (l *Logger) SetProperty(key string, value interface{}) {
	l.props[key] = value
	l.console.SetProperty(key, value)
}

func (l *Logger) log(level log.Level, message string, props log.Props) {
	if !l.IsEnabled(level) {
		return
	}

	write := func() {
		props = l.props.Merge(props)
		trx, err := l.db.Begin()
		if err != nil {
			l.console.Error(errors.Wrap(err, "failed to open transaction"))
			return
		}
		trx.NoLogs()

		message = log.Parse(message, props, false)

		_, err = trx.Execute(
			"INSERT INTO logs (tag, level, text, created_on, properties) VALUES ($1, $2, $3, $4, $5)",
			l.tag, level.String(), message, time.Now(), props,
		)

		if err != nil {
			l.console.Error(errors.Wrap(err, "failed to insert log"))
			err = trx.Rollback()
			if err != nil {
				l.console.Error(errors.Wrap(err, "failed to rollback transaction"))
			}
		} else {
			err = trx.Commit()
			if err != nil {
				l.console.Error(errors.Wrap(err, "failed to commit transaction"))
			}
		}
	}

	if env.IsTest() {
		write()
	} else {
		go write()
	}
}<commit_msg>bugfix: make database logs run sync (#460)<commit_after>package database

import (
	"fmt"
	"strings"
	"time"

	"github.com/getfider/fider/app/pkg/errors"
	"github.com/getfider/fider/app/pkg/log/console"

	"github.com/getfider/fider/app/pkg/dbx"
	"github.com/getfider/fider/app/pkg/env"
	"github.com/getfider/fider/app/pkg/log"
)

// Logger writes logs to a SQL database
type Logger struct {
	db      *dbx.Database
	console log.Logger
	level   log.Level
	tag     string
	props   log.Props
}

// NewLogger creates a new Logger
func NewLogger(tag string, db *dbx.Database) *Logger {
	level := strings.ToUpper(env.GetEnvOrDefault("LOG_LEVEL", ""))
	return &Logger{
		tag:     tag,
		db:      db,
		console: console.NewLogger(tag),
		level:   log.ParseLevel(level),
		props:   make(log.Props, 0),
	}
}

// SetLevel increases/decreases current log level
func (l *Logger) SetLevel(level log.Level) {
	l.level = level
	l.console.SetLevel(level)
}

// IsEnabled returns true if given level is enabled
func (l *Logger) IsEnabled(level log.Level) bool {
	return level >= l.level
}

// Debug logs a DEBUG message
func (l *Logger) Debug(message string) {
	l.console.Debug(message)
	l.log(log.DEBUG, message, nil)
}

// Debugf logs a DEBUG message
func (l *Logger) Debugf(message string, props log.Props) {
	l.console.Debugf(message, props)
	l.log(log.DEBUG, message, props)
}

// Info logs a INFO message
func (l *Logger) Info(message string) {
	l.console.Info(message)
	l.log(log.INFO, message, nil)
}

// Infof logs a INFO message
func (l *Logger) Infof(message string, props log.Props) {
	l.console.Infof(message, props)
	l.log(log.INFO, message, props)
}

// Warn logs a WARN message
func (l *Logger) Warn(message string) {
	l.console.Warn(message)
	l.log(log.WARN, message, nil)
}

// Warnf logs a WARN message
func (l *Logger) Warnf(message string, props log.Props) {
	l.console.Warnf(message, props)
	l.log(log.WARN, message, props)
}

// Errorf logs a ERROR message
func (l *Logger) Errorf(message string, props log.Props) {
	l.console.Errorf(message, props)
	l.log(log.ERROR, message, props)
}

// Error logs a ERROR message
func (l *Logger) Error(err error) {
	if err != nil {
		l.log(log.ERROR, err.Error(), nil)
	} else {
		l.log(log.ERROR, "nil", nil)
	}
	l.console.Error(err)
}

// Write writes len(p) bytes from p to the underlying data stream.
func (l *Logger) Write(p []byte) (int, error) {
	l.console.Write(p)
	l.Debug(fmt.Sprintf("%s", p))
	return len(p), nil
}

// New returns a copy of current logger with empty context
func (l *Logger) New() log.Logger {
	return NewLogger(l.tag, l.db)
}

// SetProperty with given key:value into current logger context
func (l *Logger) SetProperty(key string, value interface{}) {
	l.props[key] = value
	l.console.SetProperty(key, value)
}

func (l *Logger) log(level log.Level, message string, props log.Props) {
	if !l.IsEnabled(level) {
		return
	}

	props = l.props.Merge(props)
	trx, err := l.db.Begin()
	if err != nil {
		l.console.Error(errors.Wrap(err, "failed to open transaction"))
		return
	}
	trx.NoLogs()

	message = log.Parse(message, props, false)

	_, err = trx.Execute(
		"INSERT INTO logs (tag, level, text, created_on, properties) VALUES ($1, $2, $3, $4, $5)",
		l.tag, level.String(), message, time.Now(), props,
	)

	if err != nil {
		l.console.Error(errors.Wrap(err, "failed to insert log"))
		err = trx.Rollback()
		if err != nil {
			l.console.Error(errors.Wrap(err, "failed to rollback transaction"))
		}
	} else {
		err = trx.Commit()
		if err != nil {
			l.console.Error(errors.Wrap(err, "failed to commit transaction"))
		}
	}
}
{'fraction_non_alphanumeric': 0.14867280979232567, 'fraction_numerical': 0.0020629899601155273, 'mean_word_length': 3.6885880077369437, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5144137', 'n_tokens_mistral': 2763, 'n_tokens_neox': 2666, 'n_words': 734}