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

Added a Logger that can log messages in different levels. #156

Merged
merged 1 commit into from
Jun 22, 2017

Conversation

brototyp
Copy link
Member

@brototyp brototyp commented Jun 5, 2017

Closes : #147

  • Add Documentation to the new property
  • Add Documentation in the Readme
  • Add Documentation on how to write the own Logger
  • Add Changelog entry

@brototyp brototyp added the swift label Jun 5, 2017
@brototyp brototyp added this to the 4.1.0 milestone Jun 5, 2017
@@ -43,6 +43,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return
}
PiwikTracker.configureSharedInstance(withSiteID: siteid, baseURL: baseurl)
PiwikTracker.shared?.logger?.minLevel = .info
Copy link
Contributor

Choose a reason for hiding this comment

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

I know it is not purpose of this PR but,
I would really suggest staying away from singleton and shared instances.

A design goal would be to run multiple trackers instances in the same app.

Let the consumer of the framework create their own shared instance if they want.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe create an issue for tracking this refactoring if we think it is a good idea.

Copy link
Member Author

Choose a reason for hiding this comment

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

There is a ticket (#118) to ensure the Tracker can be instantiated multiple times and I agree that it is necessary to do so. But I don't think we should remove the default, shared logger. I think most users just use a single tracker and are used to have a shared instance. At the same time it doesn't break any other features.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree with @brototyp , I also think that users would like to have a shared instance to as a convenient way to use a logger. More ambitious users can use their own instances of loggers.

Copy link
Contributor

Choose a reason for hiding this comment

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

My experience from looking at a lot of iOS/Mac code is a pattern of overusing singletons. I am just trying to avoid adding to this problem :)

I personally do not see any reason for the framework to provide a singleton. A user that really want to use the singleton pattern in their code can just declare a public variable in the AppDelegate or introduce a Tracker class with a shared variable or a global variable.

Up to you. I am just on a personal crusade to reduce the use of singletons.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe look at Alamofire if you want to offer a "default" instance without having to instantiate one.

@@ -0,0 +1,55 @@
import Foundation

public enum LogLevel: Int {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it have to be public?

Copy link
Member Author

Choose a reason for hiding this comment

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

See the comment below.

}
}

public protocol Logger {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it have to be public?

Copy link
Member Author

Choose a reason for hiding this comment

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

See the comment below.


public protocol Logger {
var minLevel: LogLevel { get set }
func log(message: ()->(String), with level: LogLevel)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could capture file, function line number in the logs

func log(_ message: Void -> String, with level: LogLevel, file: String = #file, function: String = #function, line: Int = #line )

the extension methods below also needs the same params for this to work

Copy link
Member Author

Choose a reason for hiding this comment

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

Love it! Good idea!

Copy link
Member Author

Choose a reason for hiding this comment

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

Just changed this. It's great!

}
}

final class DefaultLogger: Logger {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would consider making this a struct.
But final class is almost as good :)

Copy link
Member Author

Choose a reason for hiding this comment

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

I rather like to use structs for data structures whose main task is to hold data. For things like this, where the main task is to "do things" or "define behavior" I like to use classes. What do you think about this?

@@ -22,6 +22,8 @@ final public class PiwikTracker: NSObject {
private var queue: Queue
internal let siteId: String

public var logger: Logger? = DefaultLogger(minLevel: .warning)
Copy link
Contributor

Choose a reason for hiding this comment

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

I would probably make this non optional and not expose the Logger implementations outside the framework.

If the consumer of the framework needs to change the logging level I would hide that behind a method on the PiwikTracker class, e.g. enableDebugLogging that creates and assign new immutable DefaultLogger instance with an appropriate log level.

Copy link
Member Author

Choose a reason for hiding this comment

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

See the comment below.

Copy link
Contributor

Choose a reason for hiding this comment

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

I still suggest making the logger non optional even if exposing it as a property.
The code will become more easy to write and reason about and the user will be explicitly forced to provide a value.

You can provide a default implementation of the ´NoOpLogger´ that user can set to make it easy for them.

Copy link
Member Author

Choose a reason for hiding this comment

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

I created a DisabledLogger.

@@ -22,6 +22,8 @@ final public class PiwikTracker: NSObject {
private var queue: Queue
internal let siteId: String

public var logger: Logger? = DefaultLogger(minLevel: .warning)

internal static var _sharedInstance: PiwikTracker?
Copy link
Contributor

Choose a reason for hiding this comment

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

I personally think _ prefixing is a thing of the past but accept if someone else thinks otherwise :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you think omitting the underscore would be best?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would not use '_' in any variable or instance names.

Often if you find yourself facing problems with naming resorting to using _ or other type of prefix/post fix to avoid naming conflicts it is an indication of a design problem.

@@ -55,6 +57,7 @@ final public class PiwikTracker: NSObject {

internal func queue(event: Event) {
guard !isOptedOut else { return }
logger?.verbose(){ return "Queued event: \(event)" }
Copy link
Contributor

Choose a reason for hiding this comment

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

Would

logger?.verbose { "Queued event: \(event)" }

work?

Copy link
Contributor

@minitrue22 minitrue22 Jun 5, 2017

Choose a reason for hiding this comment

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

func verbose(_ message: @autoclosure ()->(String)) { ... }

will allow and even conciser syntax
verbose("123")

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh! I didn't know about the @autoclosure. It's perfect for this use case. Will add it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Just changed this. It's great!

Podfile.lock Outdated
@@ -1,8 +1,8 @@
PODS:
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure the .lock file should be tracked by Git.
Maybe add it to .gitignore?

Copy link
Member Author

Choose a reason for hiding this comment

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

Generally I like to track the .lock file to define which exact versions this is ensured to work with. In this case, since there are only dependencies for the tests, I agree.

@brototyp
Copy link
Member Author

brototyp commented Jun 6, 2017

One core feature I wanted to go for in this implementation is a logger that can be replaced. So one could write an adapter for their own Logger (Cocoalumberjack, XCGLogger, ...). That's why the Logger protocol and LogLevel enum as well as there logger property of the Tracker are public.

@brototyp brototyp force-pushed the feature/147-logger branch 2 times, most recently from 4a177ce to cd80305 Compare June 7, 2017 15:54
@minitrue22
Copy link
Contributor

minitrue22 commented Jun 13, 2017

One core feature I wanted to go for in this implementation is a logger that can be replaced. So one could write an adapter for their own Logger (Cocoalumberjack, XCGLogger, ...). That's why the Logger protocol and LogLevel enum as well as there logger property of the Tracker are public.

Ok, I understand, maybe a good idea.

I just think logging is not a core feature for a consumer of the framework. I almost think the framework should never log anything when used in production. Any information that needs to reach the consumer should be a return values or exception. Logging in the context of this framework is only for developers of the framework it self. This will force the developer of the framework to think about the design.

But I understand the purpose and it is ok.


func log(_ message: @autoclosure () -> String, with level: LogLevel, file: String = #file, function: String = #function, line: Int = #line) {
guard level.rawValue >= minLevel.rawValue else { return }
print("PiwikTracker [\(level.shortcut)] \(message())")
Copy link
Contributor

Choose a reason for hiding this comment

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

Forgot about this.
print is not thread safe (I think).
If this method is called from different thread the print statements will be jumbled up.

You can solve that by doing the printing on a serial queue.
If you do that just must be careful about running message() on the calling thread before switching to a serial queue or you might get crashes depending on what the provided closure does.

You should probably add a comment on the Logger protocol that the implementation needs to deal with any threading themselves. Of instead of exposing the Logger to the consumer expose an Appender the consumer can set on the Logger and let Logger handle the threading.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right! Thanks for pointing this out!

@brototyp brototyp merged commit 046965d into develop Jun 22, 2017
@brototyp brototyp deleted the feature/147-logger branch October 14, 2017 10:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants