<commit_before>// Package log implements basic but useful request logging middleware.
package log

import (
	"log"
	"net/http"
	"os"

	"github.com/mholt/caddy/middleware"
)

// New instantiates a new instance of logging middleware.
func New(c middleware.Controller) (middleware.Middleware, error) {
	var logWhat, outputFile, format string
	var logger *log.Logger

	for c.Next() {
		c.Args(&logWhat, &outputFile, &format)

		if logWhat == "" {
			return nil, c.ArgErr()
		}
		if outputFile == "" {
			outputFile = defaultLogFilename
		}
		switch format {
		case "":
			format = defaultReqLogFormat
		case "{common}":
			format = commonLogFormat
		case "{combined}":
			format = combinedLogFormat
		}
	}

	// Open the log file for writing when the server starts
	c.Startup(func() error {
		var err error
		var file *os.File

		if outputFile == "stdout" {
			file = os.Stdout
		} else if outputFile == "stderr" {
			file = os.Stderr
		} else {
			file, err = os.OpenFile(outputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
			if err != nil {
				return err
			}
		}

		logger = log.New(file, "", 0)
		return nil
	})

	return func(next http.HandlerFunc) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {
			sw := middleware.NewResponseRecorder(w)
			next(sw, r)
			rep := middleware.NewReplacer(r, sw)
			logger.Println(rep.Replace(format))
		}
	}, nil
}

const (
	defaultLogFilename  = "access.log"
	commonLogFormat     = `{remote} ` + middleware.EmptyStringReplacer + ` [{when}] "{method} {uri} {proto}" {status} {size}`
	combinedLogFormat   = commonLogFormat + ` "{>Referer}" "{>User-Agent}"`
	defaultReqLogFormat = commonLogFormat
)<commit_msg>Rewrote access log middleware<commit_after>// Package log implements basic but useful request (access) logging middleware.
package log

import (
	"log"
	"net/http"
	"os"

	"github.com/mholt/caddy/middleware"
)

// New instantiates a new instance of logging middleware.
func New(c middleware.Controller) (middleware.Middleware, error) {
	rules, err := parse(c)
	if err != nil {
		return nil, err
	}

	// Open the log files for writing when the server starts
	c.Startup(func() error {
		for i := 0; i < len(rules); i++ {
			var err error
			var file *os.File

			if rules[i].OutputFile == "stdout" {
				file = os.Stdout
			} else if rules[i].OutputFile == "stderr" {
				file = os.Stderr
			} else {
				file, err = os.OpenFile(rules[i].OutputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
				if err != nil {
					return err
				}
			}

			rules[i].Log = log.New(file, "", 0)
		}

		return nil
	})

	return func(next middleware.HandlerFunc) middleware.HandlerFunc {
		return Logger{Next: next, Rules: rules}.ServeHTTP
	}, nil
}

func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	for _, rule := range l.Rules {
		if middleware.Path(r.URL.Path).Matches(rule.PathScope) {
			responseRecorder := middleware.NewResponseRecorder(w)
			status, err := l.Next(responseRecorder, r)
			rep := middleware.NewReplacer(r, responseRecorder)
			rule.Log.Println(rep.Replace(rule.Format))
			return status, err
		}
	}
	return l.Next(w, r)
}

func parse(c middleware.Controller) ([]LogRule, error) {
	var rules []LogRule

	for c.Next() {
		args := c.RemainingArgs()

		if len(args) == 0 {
			// Nothing specified; use defaults
			rules = append(rules, LogRule{
				PathScope:  "/",
				OutputFile: defaultLogFilename,
				Format:     defaultLogFormat,
			})
		} else if len(args) == 1 {
			// Only an output file specified
			rules = append(rules, LogRule{
				PathScope:  "/",
				OutputFile: args[0],
				Format:     defaultLogFormat,
			})
		} else {
			// Path scope, output file, and maybe a format specified

			format := defaultLogFormat

			if len(args) > 2 {
				switch args[2] {
				case "{common}":
					format = commonLogFormat
				case "{combined}":
					format = combinedLogFormat
				}
			}

			rules = append(rules, LogRule{
				PathScope:  args[0],
				OutputFile: args[1],
				Format:     format,
			})
		}
	}

	return rules, nil
}

type Logger struct {
	Next  middleware.HandlerFunc
	Rules []LogRule
}

type LogRule struct {
	PathScope  string
	OutputFile string
	Format     string
	Log        *log.Logger
}

const (
	defaultLogFilename = "access.log"
	commonLogFormat    = `{remote} ` + middleware.EmptyStringReplacer + ` [{when}] "{method} {uri} {proto}" {status} {size}`
	combinedLogFormat  = commonLogFormat + ` "{>Referer}" "{>User-Agent}"`
	defaultLogFormat   = commonLogFormat
)
{'fraction_non_alphanumeric': 0.13146167557932265, 'fraction_numerical': 0.004010695187165776, 'mean_word_length': 3.333011583011583, 'pattern_counts': {'":': 5, '<': 4, '<?xml version=': 0, '>': 8, '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': '314996', 'n_tokens_mistral': 1746, 'n_tokens_neox': 1534, 'n_words': 398}