Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logger #191

Closed
wants to merge 24 commits into from
Closed

Logger #191

wants to merge 24 commits into from

Conversation

mcheviron
Copy link

By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

Problem/Feature

Issue #182

Description of Changes:

This PR introduces a logger template and updates the main template to include the logger. Here are the detailed changes:

  • Logger Template Added: A new template for the logger has been added at:

    • cmd/template/framework/files/main/logger.go.tmpl
      • This template provides a basic structure for implementing logging functionality.
      • The logger is designed to be placed in the internal directory, offering an initial setup that can be extended as needed.
  • Main Template Updated: The main template file has been updated to incorporate the logger.

    • cmd/template/framework/files/main/main.go.tmpl
      • Modifications include the integration of the logger template.
      • Additional comments and explanations for extending the logger have been provided.
  • program.go Updated: The program.go file has been updated to inject the logger.go file in the internal path.

    • cmd/program/program.go
  • main.go Updated: The main.go file has been updated to embed the logger template.

    • cmd/template/framework/main.go

Checklist

  • [ x ] I have self-reviewed the changes being requested
  • [ x ] I have updated the documentation (if applicable)

Copy link
Collaborator

@Ujstor Ujstor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are using 'log.info' only in 'main.go'. Could you include logging across all templates where it is appropriate? I thought that was the initial idea?

Documentation updates are also needed. You can add the new tree structure in 'index.md' and provide a brief explanation in 'creating-project.md'.

Please check why tests are not passing

cmd/template/framework/files/main/logger.go.tmpl Outdated Show resolved Hide resolved
cmd/program/program.go Outdated Show resolved Hide resolved
@mcheviron
Copy link
Author

You are using 'log.info' only in 'main.go'. Could you include logging across all templates where it is appropriate? I thought that was the initial idea?

OK. That's why I wanted to clarify what I was going to do in prev comment because I thought all of them use the same main.go template. I see now that fiber uses a different one, that's it or are there other templates to take into consideration?

Documentation updates are also needed. You can add the new tree structure in 'index.md' and provide a brief explanation in 'creating-project.md'.

I'll add it. I just saw it

Please check why tests are not passing

I didn't read the CI/CD pipeline so I'm still unaware of what it does. I'll take a look and if I'm stuck I'll ping you.

Cheers!

PS: Please ping me if i'm missing anything else. Thanks!

@mcheviron
Copy link
Author

@Ujstor Hi again. After an illegal amount of time I think I know why the CI/CD pipeline is angry at me:

jobs:
  install_dependencies:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Go
        uses: actions/setup-go@v4
        with:
          go-version: "1.20"
      - name: Install Dependencies
        run: go mod download
  framework_matrix:
    needs: install_dependencies
    strategy:
      matrix:
        framework:
          [chi, gin, fiber, gorilla/mux, httprouter, standard-library, echo]
        driver:
          [mysql, postgres, sqlite, mongo, redis, none]
        goVersion: ["1.20"]

slog was experimental in 1.20 and the actual release is in 1.21

That's why I get this fuckery:

 Running [/home/runner/golangci-lint-1.55.2-linux-amd64/golangci-lint run --out-format=github-actions --path-prefix=chi --timeout=5m] in [/home/runner/work/go-blueprint/go-blueprint/chi] ...
  level=warning msg="[runner] Can't run linter goanalysis_metalinter: buildir: failed to load package : could not load export data: no export data for \"log/slog\""
  level=error msg="Running error: 1 error occurred:\n\t* can't run linter goanalysis_metalinter: buildir: failed to load package : could not load export data: no export data for \"log/slog\"\n\n"
  
  Error: golangci-lint exit with code 3
  Ran golangci-lint in 412ms

I think. I tried act on my local machine but it kind of worked so I'm not sure if I'm missing something. Anyways, what do you believe we should do?

Also added the template to fiber as well. I just need to create a subdirectory in the internal directory and that's all, I believe. Cheers

@Ujstor
Copy link
Collaborator

Ujstor commented Feb 25, 2024

@mcheviron

I believe I may have misunderstood some things around implementation. I envision an implementation where, after a user creates a 'go-blueprint' project, a custom logger is automatically integrated into all generated template files (such as 'server.go,' 'database.go,' 'routes.go,' etc., depending on the frameworks, databases, or advanced options selected).

This approach would provide users with comprehensive examples of how the logger is utilized throughout the entire project. Consequently, users would gain a better understanding of its usage and be able to implement it more effectively in their own projects

Check cons in program.go and logic how new dir/subdir is created.

In generate-linter.yml, we can change the Go version, and the linter would not encounter any problems.

Sorry for delay, I am little busy lately.

@mcheviron
Copy link
Author

Sorry for delay, I am little busy lately.

I totally understand. I am as well. I think I have a better understanding of what you want and I think it's a good idea. I'll try to get into it as soon as possible and if I feel like I'm being too opinionated, i'll comment out the code but leave it there in case the user wishes to uncomment it.

Do you want me to update the linter myself? I believe updating the framework_matrix task would solve the issue but since it's the CI pipeline, I didn't want to touch it without bringing it up. Cheers

@Ujstor
Copy link
Collaborator

Ujstor commented Feb 26, 2024

Yeah, you can update the Go version in the linter, and that should do the trick.

Your logger is fine, use it in other packages and replace old logs with the new implementation. The current one is not ideal:

log.Fatalf(fmt.Sprintf("db down: %v", err))

Don't rush with code, there is another big PR that introduces websockets, and there are a lot of changes in templates. We can find the best way to implement it after ws PR is merged.

Hit me up on Discord in DM; my username is the same, and we can discuss and find the best solution.

@mcheviron
Copy link
Author

Hit me up on Discord in DM; my username is the same, and we can discuss and find the best solution.

Hi sorry for the delay. I tried pinging you but you can't be added via strangers.

Yeah, you can update the Go version in the linter, and that should do the trick.

Your logger is fine, use it in other packages and replace old logs with the new implementation. The current one is not ideal:

I will generate some projects this weekend and reread our convo to make sure I put the logger in the right subdir and then use it throughout the projects. If you add me, i'll ping you there before I commit.

P.S: I'll update the linter version as well

@mcheviron
Copy link
Author

ci/cd is updated to use 1.22 while linting. it's better to sync the versions, 1.21.1 is used somewhere (that's just a suggestion, do what's best for the proj)

logger is put in its own pkg under internal

i added a Fatal method to the logger, it called log.Fatal under the hood. slog offers no API for fataling

if you want it to be used in the db initialisation for all the templates, we'd either do a dependancy injection on the New() for each db and use the logger therein. but since log.Fatal is used inside, I don't know if it's worth it. but it's doable

Copy link
Contributor

@H0llyW00dzZ H0llyW00dzZ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changed for this all database template it going wrong by putting nil and error

}
s := &service{db: db}
return s
return s, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to set nil for this, because nil is already handled by the caller when you use it somewhere.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to set nil for this, because nil is already handled by the caller when you use it somewhere.

check with uj and his discussion with me. we don't want fatal's. this is Go not Java. The idea is to return errors and handle the errors via a tracing mechanism on the top level then terminating on certain errors and ultimately let the user choose what is fatal and what's not instead of greping for all fatals in the project like i did and changing them because that's lackluster in any production environment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to set nil for this, because nil is already handled by the caller when you use it somewhere.

check with uj and his discussion with me. we don't want fatal's. this is Go not Java. The idea is to return errors and handle the errors via a tracing mechanism on the top level then terminating on certain errors and ultimately let the user choose what is fatal and what's not instead of greping for all fatals in the project like i did and changing them because that's lackluster in any production environment.

It's not about Go or Java. The concern is about handling errors properly. Just imagine if you don't use fatal, then trying to connect to a database that is down could lead to unexpected behavior or crashes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the current implementation is already corrected using fatal and without nil

example:

https://github.com/Melkeydev/go-blueprint/blob/main/cmd/template/dbdriver/files/service/mysql.tmpl

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this how it work with fatal

$ go run cmd/api/main.go
2024/04/07 16:28:30 [H0llyW00dzZ Firewall] [INFO] Connecting to database: *********
2024/04/07 16:28:31 [H0llyW00dzZ Firewall] [FATAL] Failed to create firewall table: Error 1045 (28000): Access denied for user 'root'@'127.0.0.1' (using password: YES)
exit status 1

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also when it correct it show like this

$ go run cmd/api/main.go
2024/04/07 20:09:06 [H0llyW00dzZ Firewall] [INFO] Connecting to database: *********
2024/04/07 20:09:08 [H0llyW00dzZ Firewall] [INFO] Successfully initialized the firewall table.
2024/04/07 20:09:09 [H0llyW00dzZ Project] [INFO] Starting server on :8080

 ┌───────────────────────────────────────────────────┐ 
 │                H0llyW00dzZ Project                │ 
 │                   Fiber v2.52.4                   │ 
 │               http://127.0.0.1:8080               │ 
 │       (bound on host 0.0.0.0 and port 8080)       │ 
 │                                                   │ 
 │ Handlers ............ 539 Processes ........... 1 │ 
 │ Prefork ....... Disabled  PID ............. 28260 │ 
 └───────────────────────────────────────────────────┘ 

Hey - I thought I should chime in here because this great PR has definitely been out for too long without resolution. I discussed with the internal team, and we believe for go-blueprint that thing that makes sense is to throw Fatal if there is a DB connectivity error.

I am thinking about this from the perspective of a user, and if there is a an error connecting to the DB, but the app still spins up, it might be less than perfect user experience?

My intention is to not come in here and throw down a hammer, but again, asking for input on this. From my personal thinking, fatal on db error makes sense

Copy link
Contributor

@H0llyW00dzZ H0llyW00dzZ Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also when it correct it show like this

$ go run cmd/api/main.go
2024/04/07 20:09:06 [H0llyW00dzZ Firewall] [INFO] Connecting to database: *********
2024/04/07 20:09:08 [H0llyW00dzZ Firewall] [INFO] Successfully initialized the firewall table.
2024/04/07 20:09:09 [H0llyW00dzZ Project] [INFO] Starting server on :8080

 ┌───────────────────────────────────────────────────┐ 
 │                H0llyW00dzZ Project                │ 
 │                   Fiber v2.52.4                   │ 
 │               http://127.0.0.1:8080               │ 
 │       (bound on host 0.0.0.0 and port 8080)       │ 
 │                                                   │ 
 │ Handlers ............ 539 Processes ........... 1 │ 
 │ Prefork ....... Disabled  PID ............. 28260 │ 
 └───────────────────────────────────────────────────┘ 

Hey - I thought I should chime in here because this great PR has definitely been out for too long without resolution. I discussed with the internal team, and we believe for go-blueprint that thing that makes sense is to throw Fatal if there is a DB connectivity error.

I am thinking about this from the perspective of a user, and if there is a an error connecting to the DB, but the app still spins up, it might be less than perfect user experience?

My intention is to not come in here and throw down a hammer, but again, asking for input on this. From my personal thinking, fatal on db error makes sense

I am thinking about this from the perspective of a user, and if there is a an error connecting to the DB, but the app still spins up, it might be less than perfect user experience? For that, it's better to use Fatal for the database because, for the database, it's recommended to set it in init to initialize first before the app runs. So, when the database fails, the app won't run.

I have already implemented this, as shown in the example below:

func init() {
	// Initialize the logger here with the necessary parameters.
	Logger.InitializeLogger("H0llyW00dzZ Firewall")
	// Initialize the context and cancel function
	schedulerCtx, cancelScheduler = context.WithCancel(context.Background())

	// Check if the necessary environment variables for the database connection are set.
	if os.Getenv(database.EnvMYSQLDBName) != "" && os.Getenv(database.EnvMYSQLDBPassword) != "" &&
		os.Getenv(database.EnvMYSQLDBUsername) != "" && os.Getenv(database.EnvMYSQLDBPort) != "" &&
		os.Getenv(database.EnvMYSQLDBHost) != "" {
		// Initialize the database service.
		db = database.New()

		// Initialize the firewall table if it doesn't exist.
		if err := createFirewallTable(); err != nil {
			Logger.LogFatalf("Failed to initialize firewall table: %v", err)
		} else {
			Logger.LogInfo("Successfully initialized the firewall table.")
		}
	}
}

also note that keeping fatal in New in this

func New() Service {
	// Opening a driver typically will not attempt to connect to the database.
	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", username, password, host, port, dbname))
	if err != nil {
		// This will not be a connection error, but a DSN parse error or
		// another initialization error.
		log.Fatal(err)
	}

it will show the failure in // Initialize the firewall table if it doesn't exist. plus it won't making new connection during Initialize because of this singleton pattern:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, ignore the line schedulerCtx, cancelScheduler = context.WithCancel(context.Background()) in example. That is for my worker goroutines.

Copy link
Contributor

@H0llyW00dzZ H0llyW00dzZ Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another Example Method Using Recovery for Panic

func init() {
    // Initialize the logger here with the necessary parameters.
    Logger.InitializeLogger("H0llyW00dzZ Firewall")
    // Initialize the context and cancel function
    schedulerCtx, cancelScheduler = context.WithCancel(context.Background())

    // Check if the necessary environment variables for the database connection are set.
    if os.Getenv(database.EnvMYSQLDBName) != "" && os.Getenv(database.EnvMYSQLDBPassword) != "" &&
        os.Getenv(database.EnvMYSQLDBUsername) != "" && os.Getenv(database.EnvMYSQLDBPort) != "" &&
        os.Getenv(database.EnvMYSQLDBHost) != "" {
        // Set up a defer to recover from any panic that might occur in database initialization.
        defer func() {
            if r := recover(); r != nil {
                Logger.LogFatalf("Failed to initialize the database: %v", r)
            }
        }()

        // Initialize the database service.
        db = database.New()

        // Initialize the firewall table if it doesn't exist.
        if err := createFirewallTable(); err != nil {
            Logger.LogFatalf("Failed to initialize firewall table: %v", err)
        } else {
            Logger.LogInfo("Successfully initialized the firewall table.")
        }
    }
}

It will show like this:

$ go run cmd/api/main.go
2024/04/10 08:36:54 [H0llyW00dzZ Firewall] [INFO] Initialization to database: *********
2024/04/10 08:36:55 [H0llyW00dzZ Firewall] [FATAL] Failed to initialize the database: Error 1045 (28000): Access denied for user 'root'@'127.0.0.1' (using password: YES)
exit status 1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And another issue that can occur during runtime (e.g., when a user is interacting with the database but the database goes down) can be covered by panic recovery in the middleware (e.g., if the error is not handled correctly).

That's it

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

err := s.db.PingContext(ctx)
if err != nil {
log.Fatalf(fmt.Sprintf("db down: %v", err))
return nil, fmt.Errorf("error pinging db: %w", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this one as well, it's important to use fatal.

}

return map[string]string{
"message": "It's healthy",
}
}
}, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this one as well, you don't have to set nil.

Copy link
Contributor

@H0llyW00dzZ H0llyW00dzZ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach is better, as it avoids explicit nil and uses panic when initialization fails.

Copy link
Contributor

@H0llyW00dzZ H0llyW00dzZ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by the way this still bad using nil


return map[string]string{
"message": "It's healthy",
}
}
}, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach avoids explicit nil values for Redis and MySQL connections, which is beneficial for large codebases and scalability. It's better to remove explicit nil handling, as it will be handled by the caller elsewhere.

Copy link
Contributor

@H0llyW00dzZ H0llyW00dzZ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just letting you know again

source.File = filepath.Base(source.File)
}

// if used in AWS or GCP (and Azure?), they'll add the timestamp themselves. Else uncomment to get timestamps
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to enable this independently, even when used in AWS, GCP, or Azure, because the logger should be configured independently instead of depending on the cloud provider or whatever it is for timestamp handling.

@Ujstor
Copy link
Collaborator

Ujstor commented May 18, 2024

I am closing this repo, assuming there is no interest to continue implementation.

@Ujstor Ujstor closed this May 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants