Это моя первая статья на Хабре, пишу её по ходу своего обучения. За подсказки буду очень благодарен.

Здесь автор говорит о том, какими должны быть логи. В своем цикле статей я покажу вам как можно сделать подобное логирование своими руками на GO.

Итак начнём. Для начала нам необходимо сделать простейший tcp сервер:

tcp сервер на golang
package main

import (

	"log"
	"net"
)

func main() {

	//вешаем сервер на 2000 порт
	l, err := net.Listen("tcp", ":2000")
	if err != nil {
		log.Fatal(err)
	}
	defer l.Close()
	for {
		//В вечном цикле "слушаем" 2000 порт. Ждём подключения
		conn, err := l.Accept()
		if err != nil {
			log.Fatal(err)
		}
		//К нам кто то подключился. Откроем для него свою горутину
		go func(c net.Conn) {

			//создаём буфер для получения данных из вне.
			buf := make([]byte, 1024)

			//открываем ещё один вечный цикл для того чтобы можно было получить n количество сообщений,
			// а не одно
			for {
				//Полученную информацию пишем в буфер
				c.Read(buf)

				log.Print(string(buf))
			}
		}(conn)
	}
}


Теперь запускаем наш сервер и в новом окошке терминала делаем как-то так:

Посмотреть
image

Теперь нам надо распознавать команды. Сделаем мини api.

//типы лог сообщений
Debug|][|что писать в лог буфер->150 записей
Info|][|что писать в лог буфер->100 записей
Warn|][|что писать в лог буфер->50 записей
Error|][|что писать в лог буфер->25 записей
Fatal|][|что писать в лог буфер->0 записей

//данные с буфера переместить в бд
UnloadTheCacheDB|][|null

//очистить буфер не перемещая данные в бд
ClearCache|][|null

Теперь код:

Код
package main

import (
	"log"
	"net"
	"strings"
)

func main() {
	l, err := net.Listen("tcp", ":2000")
	if err != nil {
		log.Fatal(err)
	}
	defer l.Close()
	for {

		conn, err := l.Accept()
		if err != nil {
			log.Fatal(err)
		}
		go func(c net.Conn) {
			buf := make([]byte, 1024)

			for {
				c.Read(buf)
				result := strings.Split(string(buf), "|][|")
				switch result[0] {

				case "Debug":
					log.Print("Debug log сообщение=" + result[1])
				case "Info":
					log.Print("Info log сообщение=" + result[1])
				case "Warn":
					log.Print("Warn log сообщение=" + result[1])
				case "Error":
					log.Print("Error log сообщение=" + result[1])
				case "Fatal":
					log.Print("Fatal log сообщение=" + result[1])

				case "UnloadTheCacheDB":

				case "ClearCache":

				default:
					log.Print("case Default\n")
					c.Close()
					return
				}
			}
			c.Close()
		}(conn)
	}
}


Команда == лог сообщения различаем.

Посмотреть
image

Теперь у вас два пути. Прикрутить сюда мемкеш и писать в бд пачками из него, или сидеть и ждать следующей статьи, где будет исправлен конфликт между go рутинами.

Вот полный код. Работает всё отлично, правда, если в 1 поток. Как только разберусь с блокировкой данных, обновлю статью.

Код
package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql" //можно подключить любую sql базу данных
	"log"
	"net"
	"net/smtp"
	"strings"

	"time"
)

var DB *sql.DB

var i_debug int = 0
var cacheDebug string

var i_info int = 0
var cacheInfo string

var i_warn int =0
var cacheWarn string

var i_error int = 0
var cacheError string

func init() {
	db, err := sql.Open("mysql", "db_user:db_pass@/logs_service")
	if err != nil {
		log.Fatal(err)
	}
	DB = db
}
func main() {

	l, err := net.Listen("tcp", ":2000")
	if err != nil {
		log.Fatal(err)
	}
	defer l.Close()
	for {
		conn, err := l.Accept()
		if err != nil {
			log.Fatal(err)
		}

		go func(c net.Conn) {

			buf := make([]byte, 1024)

			for {

				c.Read(buf)

				result := strings.Split(string(buf), "|][|")

				switch result[0] {

				case "Debug":
					if i_debug <= 300 {

						cacheDebug = cacheDebug + "('" + time.Now().String() + "','" + result[1] + "')"

						go DebugDB(cacheDebug)

						i_debug = 0

						cacheDebug = " "
					} else {

						cacheDebug = cacheDebug + "('" + time.Now().String() + "','" + result[1] + "'),"
					}

					i_debug++

				case "Info":
					if i_info <= 150 {
						cacheInfo = cacheInfo + "('" + time.Now().String() + "','" + result[1] + "')"
						go InfoDB(cacheInfo)
						i_info = 0
						cacheInfo = " "
					} else {
						cacheInfo = cacheInfo + "('" + time.Now().String() + "','" + result[1] + "'),"
					}
					i_info++

				case "Warn":
					if i_warn <= 50 {
						cacheWarn = cacheWarn + "('" + time.Now().String() + "','" + result[1] + "')"
						go WarnDB(cacheWarn)

						i_warn = 0
						cacheWarn = " "
					} else {
						cacheWarn = cacheWarn + "('" + time.Now().String() + "','" + result[1] + "'),"
					}
					i_warn++

				case "Error":
					if i_error == 25 {
						cacheError = cacheError + "('" + time.Now().String() + "','" + result[1] + "')"
						go ErrorDB(cacheError)

						i_error = 0
						cacheError = " "
						cacheError = cacheError + "('" + time.Now().String() + "','" + result[1] + "'),"
					}
					i_error++

				case "Fatal":
					go FatalDB(result[1])

				case "UnloadTheCacheDB":

					go DebugDB(cacheDebug)
					go InfoDB(cacheInfo)
					go WarnDB(cacheWarn)
					go ErrorDB(cacheError)

					cacheDebug = " "
					cacheInfo = " "
					cacheWarn = " "
					cacheError = " "

					i_debug = 0
					i_info = 0
					i_warn = 0
					i_error = 0
					log.Print("Бд успешно обновленна, а кеш очищен")

				case "ClearCache":
					cacheDebug = " "
					cacheInfo = " "
					cacheWarn = " "
					cacheError = " "
					i_debug = 0
					i_info = 0
					i_warn = 0
					i_error = 0
					log.Print("Кеш успешно очистили")

				default:
					log.Print("case Default\n")
					c.Close()
					return
				}

			}
		}(conn)
	}
}

//просто пише в таблицы.
func DebugDB(cache string) {
	DB.Exec("INSERT INTO debug (date,message) VALUES " + cache)
	return
}
func InfoDB(cache string) {
	DB.Exec("INSERT INTO info (date,message) VALUES " + cache)
	return
}
func WarnDB(cache string) {
	DB.Exec("INSERT INTO warn (date,message) VALUES " + cache)
	return
}
func ErrorDB(cache string) {
	DB.Exec("INSERT INTO error (date,message) VALUES " + cache)
	return
}

//А тут пишем в бд и отправляем email уведомление
func FatalDB(message string) {
	DB.Exec("INSERT INTO fatal (date,message) VALUES (?,?)", time.Now().String(), message)
	conf := map[string]string{
		"username": "example@gmail.com", //логин от любой почты на gamail
		"password": "example",           //ну и пароль
		"host":     "smtp.gmail.com",
		"port":     "587",
	}
	to := []string{
		"example99@gmail.com",
		"id324237193-6fce7d7ef@vkmessenger.com",
	}
	SendMail(conf, to, "Fatal Errors", message)
	return

}
func SendMail(conf map[string]string, to []string, subject string, msg string) {
	auth := smtp.PlainAuth(
		"",
		conf["username"],
		conf["password"],
		conf["host"],
	)
	address := fmt.Sprintf("%v:%v", conf["host"], conf["port"])
	body := []byte("Subject: " + subject + "\r\n\r\n" + msg)
	err := smtp.SendMail(
		address,
		auth,
		conf["username"],
		to,
		body,
	)
	if err != nil {
		log.Fatal(err)
	}
}


Комментарии (4)


  1. gurinderu
    03.11.2015 11:19

    Я просто оставлю это здесь
    https://github.com/alecthomas/log4go


    1. vGrabko99
      03.11.2015 17:47

      Если всё уже есть то как мне попасть на хабр ?))


  1. Core2Duo
    03.11.2015 11:27
    +1

    KISS, DRY, возможный SQL-inject при записи в БД: используется Exec с непроверенным параметром.


    1. vGrabko99
      03.11.2015 17:42

      Спасибо. В следующей статьи всё исправлю