Merge pull request #3 from dikstub-rssa/feat/logger-example

Feat/logger example
This commit is contained in:
Munawwirul Jamal
2025-08-12 15:14:56 +07:00
committed by GitHub
9 changed files with 392 additions and 33 deletions

2
.gitignore vendored
View File

@@ -30,4 +30,4 @@ config.yml
# Editor/IDE
# .idea/
# .vscode/
.vscode/*

18
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"configurations": [
{
"name": "Launch Package main API",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/main-api"
},
// {
// "name": "Launch Package excel migrator",
// "type": "go",
// "request": "launch",
// "mode": "auto",
// "program": "${workspaceFolder}/cmd/excelmigrator"
// }
]
}

4
go.mod
View File

@@ -5,8 +5,9 @@ go 1.23.0
toolchain go1.23.11
require (
github.com/karincake/apem v0.0.16-e
github.com/karincake/apem v0.0.16-g
github.com/karincake/dodol v0.0.1
github.com/karincake/lepet v0.0.1
gorm.io/gorm v1.25.10
)
@@ -24,6 +25,7 @@ require (
github.com/nxadm/tail v1.4.11 // indirect
github.com/rs/zerolog v1.33.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect

8
go.sum
View File

@@ -20,10 +20,12 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/karincake/apem v0.0.16-e h1:KGeQYmgNw8luidSWkZyGcMbW1PP3KMRCHMTEHQ7twU8=
github.com/karincake/apem v0.0.16-e/go.mod h1:cQP2sJfDrLRIiwWoaLWw/z8uAya+DWu/FpmYeinMQXM=
github.com/karincake/apem v0.0.16-g h1:jPIr/YiaJhVSftdA1PyB2tlDiQtFeTVZohO1qf0qpw0=
github.com/karincake/apem v0.0.16-g/go.mod h1:cQP2sJfDrLRIiwWoaLWw/z8uAya+DWu/FpmYeinMQXM=
github.com/karincake/dodol v0.0.1 h1:jUXmJh1r0Ei4fmHPZ6IUkoplW/V9d27L63JEl6zudL0=
github.com/karincake/dodol v0.0.1/go.mod h1:2f1NcvkvY0J3GMUkwILNDYVvRUpz0W3lpPp/Ha/Ld24=
github.com/karincake/lepet v0.0.1 h1:eq/cwn5BBg0jWZ1c/MmvhFIBma0zBpVs2LwkfDOncy4=
github.com/karincake/lepet v0.0.1/go.mod h1:U84w7olXO3BPJw2Hu6MBonFmJmPKaFjtyAj1HTu3z1A=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -54,6 +56,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@@ -9,9 +9,14 @@ type Createdto struct {
type ReadListDto struct {
Code string `json:"code"`
Name string `json:"name"`
Page int `json:"page"`
PageSize int `json:"page_size"`
NoPagination int `json:"no_pagination"`
}
type ReadDetailDto struct {
Id uint `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
}
@@ -24,3 +29,9 @@ type Updatedto struct {
type Deletedto struct {
Id uint `json:"id"`
}
type MetaDto struct {
PageNumber int `json:"page_number"`
PageSize int `json:"page_size"`
Count int64 `json:"count"`
}

View File

@@ -5,33 +5,93 @@ Any functions that are available to be used externally.
package crud
import (
"fmt"
"strconv"
dg "github.com/karincake/apem/db-gorm-pg"
d "github.com/karincake/dodol"
pl "simrs-vx/pkg/logger"
"gorm.io/gorm"
e "simrs-vx/internal/domain/main-entities/single"
e "simrs-vx/internal/domain/_template/single"
)
const source = "crud"
func Create(input e.Createdto) (*d.Data, error) {
data := e.Single{}
setData(&data, &input)
event := pl.Event{
Feature: "Create",
Source: source,
}
// Start log
event.Action = "Create"
event.Status = "started"
pl.SetLogInfo(event, input)
err := dg.I.Transaction(func(tx *gorm.DB) error {
for i := range createPreMw {
if err := createPreMw[i](&input, &data, dg.I); err != nil {
return nil
mwName := fmt.Sprintf("createPreMw[%d]", i)
event.Action = mwName
event.Status = "started"
pl.SetLogInfo(event, data)
if err := createPreMw[i](&input, &data, tx); err != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "MW_PRE_FAILED", // TODO: add to lang json
Detail: fmt.Sprintf("Pre-middleware %s failed", mwName),
Raw: mwErr,
}
return pl.SetLogError(event, data)
}
event.Status = "completed"
pl.SetLogInfo(event, nil)
}
if err := dg.I.Create(&data).Error; err != nil {
return nil
event.Action = "DBCreate"
event.Status = "started"
pl.SetLogInfo(event, data)
if result, err := CreateData(&data, tx).Error; err != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "data-create-fail",
Detail: "Database insert failed",
Raw: err,
}
return pl.SetLogError(event, data)
}
event.Status = "completed"
pl.SetLogInfo(event, nil)
for i := range createPostMw {
if err := createPostMw[i](&input, &data, dg.I); err != nil {
return nil
mwName := fmt.Sprintf("createPostMw[%d]", i)
event.Action = mwName
event.Status = "started"
pl.SetLogInfo(event, input)
if err := createPostMw[i](&input, &data, tx); err != nil {
event.Status = "failed"
event.ErrInfo = pl.ErrorInfo{
Code: "MW_POST_FAILED", // TODO: add to lang json
Detail: fmt.Sprintf("Post-middleware %s failed", mwName),
Raw: mwErr,
}
return pl.SetLogError(event, data)
}
}
event.Status = "completed"
pl.SetLogInfo(event, nil)
return nil
})
@@ -46,26 +106,129 @@ func Create(input e.Createdto) (*d.Data, error) {
}
func ReadList(input e.ReadListDto) (*d.Data, error) {
data := d.Data{}
query := dg.I
query.Find(&data)
if err := query.Error; err != nil {
return nil, err
}
err := dg.I.Transaction(func(tx *gorm.DB) error {
for i := range readListPreMw {
if err := readListPreMw[i](&input, &data, tx); err != nil {
return nil
}
}
data, meta, err := ReadListData(input, tx)
if err != nil {
return nil, err
}
for i := range readListPostMw {
if err := readListPostMw[i](&input, &data, tx); err != nil {
return nil
}
}
return nil
})
return &d.Data{
Meta: d.II{},
Meta: d.IS{
"source": source,
"structure": "list-data",
"status": "fetched",
"page_number": strconv.Itoa(meta.PageNumber),
"page_size": strconv.Itoa(meta.PageSize),
"record_totalCount": strconv.Itoa(meta.Count),
},
Data: data,
}, nil
}
func ReadDetail() {
func ReadDetail(input e.ReadDetailDto) (*d.Data, error) {
err := dg.I.Transaction(func(tx *gorm.DB) error {
for i := range readDetailPreMw {
if err := readDetailPreMw[i](&input, &data, tx); err != nil {
return nil
}
}
data, err := ReadDetailData(input, tx)
if err != nil {
return nil, err
}
for i := range readDetailPostMw {
if err := readDetailPostMw[i](&input, &data, tx); err != nil {
return nil
}
}
return nil
})
return &d.Data{
Meta: d.IS{
"source": source,
"structure": "single-data",
"status": "fetched",
},
Data: data,
}, nil
}
func Update(input e.Updatedto) (*d.Data, error) {
err := dg.I.Transaction(func(tx *gorm.DB) error {
data, err := ReadDetailData(input, tx)
if err != nil {
return nil
}
setData(&data, &input)
for i := range updatePreMw {
if err := updatePreMw[i](&input, &data, tx); err != nil {
return nil
}
}
if err := UpdateData(*data, tx); err != nil {
return nil
}
for i := range updatePostMw {
if err := updatePostMw[i](&input, &data, tx); err != nil {
return nil
}
}
return nil
})
return &d.Data{
Meta: d.IS{
"source": source,
"structure": "single-data",
"status": "updated",
},
Data: data,
}, nil
}
func Update() {
}
func Delete() {
func Delete(input e.Deletedto) (*d.Data, error) {
err := dg.I.Transaction(func(tx *gorm.DB) error {
data, err := ReadDetailData(input, tx)
if err != nil {
return nil
}
for i := range deletePreMw {
if err := deletePreMw[i](&input, &data, tx); err != nil {
return nil
}
}
if err := DeleteData(data, tx); err != nil {
return nil
}
for i := range deletePostMw {
if err := deletePostMw[i](&input, &data, tx); err != nil {
return nil
}
}
})
return &d.Data{
Meta: d.IS{
"source": source,
"structure": "single-data",
"status": "deleted",
},
Data: data,
}, nil
}

View File

@@ -1,25 +1,101 @@
package crud
import (
e "simrs-vx/internal/domain/main-entities/single"
e "simrs-vx/internal/domain/_template/single"
dg "github.com/karincake/apem/db-gorm-pg"
gh "github.com/karincake/getuk"
"gorm.io/gorm"
)
func CreateData(input e.Createdto, tx ...*gorm.DB) (*e.Single, error) {
data := e.Single{}
var db *gorm.DB
if tx != nil {
db = tx
func CreateData(input *e.Single, dbx ...*gorm.DB) (*e.Single, error) {
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
db = dg.I
tx = dg.I
}
if err := dg.I.Create(&data).Error; err != nil {
if err := tx.Create(&data).Error; err != nil {
return nil, err
}
return &data, err
}
func ReadListData(input e.ReadListDto, dbx ...*gorm.DB) ([]e.Single, *e.MetaDto, error) {
data := []e.Single{}
pagination := gh.Pagination{}
count := int64(0)
meta := e.MetaDto{}
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
tx = dg.I
}
tx = tx.
Model(&e.Single{}).
// Joins("Patient"). // if needed
// Preload("Patient"). // if needed
Scopes(gh.Filter(input)).
Count(&count).
Scopes(gh.Paginate(input, &pagination)).
Order("CreatedAt DESC")
if err := tx.Find(&data).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, &meta, nil
}
return nil, nil, err
}
meta.Count = count
meta.PageNumber = pagination.PageNumber
meta.PageSize = pagination.PageSize
return data, &meta, nil
}
func ReadDetailData(input e.ReadDetailDto, dbx ...*gorm.DB) (*e.Single, error) {
data := e.Single{}
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
tx = dg.I
}
if err := tx.First(&data, input.Id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &data, nil
}
func UpdateData(input e.Single, dbx ...*gorm.DB) error {
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
tx = dg.I
}
return tx.Save(&input).Error
}
func DeleteData(input *e.Single, dbx ...*gorm.DB) error {
var tx *gorm.DB
if len(dbx) > 0 {
tx = dbx[0]
} else {
tx = dg.I
}
return tx.Delete(input).Error
}

View File

@@ -0,0 +1,19 @@
// Package langhelper provides helper functions for language
package langhelper
import (
a "github.com/karincake/apem"
l "github.com/karincake/lepet"
)
type Cfg struct {
LangCfg *l.LangCfg `yaml:"langCfg"`
}
func Populate() {
cfg := &Cfg{
LangCfg: &l.LangCfg{},
}
a.ParseCfg(&cfg)
l.Init(cfg.LangCfg)
}

66
pkg/logger/logger.go Normal file
View File

@@ -0,0 +1,66 @@
// Package logger provides helper functions for logging
package logger
import (
"encoding/json"
lz "github.com/karincake/apem/logger-zerolog"
d "github.com/karincake/dodol"
l "github.com/karincake/lepet"
)
type Event struct {
// Context about the operation
Feature string // Feature area, e.g. "Create"
Action string // Action being performed, e.g. "DBCreate"
Source string // Source of event, usually present in each use case
Status string // e.g. "started", "failed", "success"
// Error context
ErrInfo ErrorInfo
}
type ErrorInfo struct {
Code string // used in lang json
Detail string
Raw error
}
func SetLogInfo(e Event, data any) {
dataString, _ := json.Marshal(data)
lz.O.Info().
String("source", e.Source).
String("feature", e.Feature).
String("action", e.Action).
String("status", "started").
String("input", string(dataString)).
Send()
}
func SetLogError(e Event, data any) error {
dataString, _ := json.Marshal(data)
msg := l.I.Msg(e.ErrInfo.Code)
lz.O.Error().
String("message", msg).
String("source", e.Source).
String("feature", e.Feature).
String("action", e.Action).
String("status", e.Status).
String("error_code", e.ErrInfo.Code).
String("error_detail", e.ErrInfo.Detail).
String("data", string(dataString)).
Send()
if err, ok := e.ErrInfo.Raw.(d.FieldError); ok {
return err
}
if err, ok := e.ErrInfo.Raw.(d.FieldErrors); ok {
return err
}
if e.ErrInfo.Detail != "" {
return d.FieldError{Code: e.ErrInfo.Code, Message: e.ErrInfo.Detail}
}
return d.FieldError{Code: e.ErrInfo.Code, Message: msg}
}