Hacker News new | past | comments | ask | show | jobs | submit login
Introducing HTTP Tracing (golang.org)
189 points by JepZ on Oct 4, 2016 | hide | past | favorite | 58 comments



HTTPStat https://github.com/davecheney/httpstat now uses the HTTP Tracing library to provide some pretty neat graphs, letting you visualise the HTTP Tracing output.

    $ httpstat https://news.ycombinator.com

    HTTP/1.1 200 OK
    Date: Wed, 05 Oct 2016 05:23:28 GMT
    Content-Type: text/html; charset=utf-8
    Transfer-Encoding: chunked
    Connection: keep-alive
    Vary: Accept-Encoding
    Cache-Control: private
    X-Frame-Options: DENY
    Cache-Control: max-age=0
    Strict-Transport-Security: max-age=31556900; includeSubDomains
    Server: cloudflare-nginx
    CF-RAY: 2ece7111d9ba29c4-MCT



      DNS Lookup   TCP Connection   SSL Handshake   Server Processing   Content Transfer
    [     5ms    |      152ms     |     508ms     |      1285ms       |       226ms      ]
                 |                |               |                   |                  |
        namelookup:5ms            |               |                   |                  |
                            connect:157ms         |                   |                  |
                                        pretransfer:665ms             |                  |
                                                          starttransfer:1950ms           |
                                                                                     total:2176ms


This looks awesome. Will have to check this out tomorrow.


I haven't explicitly needed HTTP tracing yet, but am glad to see it's added to the tool set specifically for debugging DNS which can be tough.

On another note, it's pretty f*cking lame that the only discussion in this post is a tangent about error checking and not even remotely related to the content.


Is it bad that the first thought that came to mind upon reading the example code on the page is "Wow, this is ugly"?

...And I don't mean the "if err" sprinkling that has a whole subthread, I mean the actual layout of the code. It's not hard to understand just.. ugly. Aesthetically. And it's hard for me to put my finger on why.. maybe it's the symbols everywhere.

I was about ready to make a remark about how I hope that's not idiomatic go code, but no, this is from the official site.

I've never wanted to like a language that makes me not want to like it as much as Go, and that's the god's honest truth.


> It's not hard to understand just.. ugly.

To me, code that's easy to understand is beautiful. If you've got different ideals, then probably go ain't the language for you.

Or use it for a while, and you may find your standards if beauty shift.


Give it a chance. I feel the opposite in many cases today because of two reasons: static typing and gofmt. In Python code I frequently find myself playing "what the hell are these objects" or "what the hell does this overloaded operator do" and in lower level code (esp. C++) it looks like a mangled jaggy mess. Linux kernel code is one of the few exceptions due to the strict style guidelines.


lots of time I see people compromise readability to chase this "beauty" and "elegance". Go makes this practically impossible.


Python is pretty readable but it doesn't look like someone smashed the keyboard while holding down the shift key.


Python is pretty hit and miss with readability.


Great to see this as part of the core language! Say what you will about Go, the core libs and tools are well designed and really useful.


I wonder if this will help any of the opentracing work? Cool to see this added as a first class citizen.


This has been one of my favorite new additions to Go 1.7. Incredibly useful for debugging persistent connection pool efficiency.


[flagged]


Every time I find code where people skipped checking for errors, I want to drink


In fairness, conditions and restarts could do the same job and would be more readable.


When you're using a language with generics and flexible syntax, you just use the Try pattern and you never aren't checking for errors.


I wouldn't make that strong of a claim. I've seen plenty of code in the try pattern that, basically, didn't check for errors.


And when working in a language that returns errors, you just use the "check the result for an error" pattern and same.


One of these things is a compiler-enforced piece of tooling; I know my errors are handled because I don't have any alternatives to handling them. The other is a code standard that people regularly ignore. If you want to assert equivalence between these two options, I suggest you show your work.


https://play.golang.org/p/X6zItReejE

> tmp/sandbox459432416/main.go:10: err declared and not used

Go won't compile unless you handle your errors.


To be fair, removing the assignment altogether in your example ignores the error much less explicitly than `try { myFunc(); } except { pass; }`


If you remove the err assignment the compiler will also complain about a value assignment mismatch:

https://play.golang.org/p/UBL0MZtscc

> tmp/sandbox307381845/main.go:8: multiple-value "html/template".New("foo").Parse() in single-value context

You would have to explicitly assign the err return value to underscore in order to ignore it.


hueving is correct, I meant something like this[1]. Of course, something like this[2] also works.

[1] https://play.golang.org/p/igdUx6qckf

[2] https://play.golang.org/p/_JW5QE_zOJ


That function does not have side effects, so it would be pointless to remove the assignment altogether.

Assigning errors to underscore is effectively the same as try { myFunc(); } except { pass; }, making them equivalent.


No, remove the entire assignment.


If you have to use a pattern, how is the compiler enforcing it? Suppose you don't use the pattern? Since you spoke only in the abstract without so much as naming your language, I'd suggest your work exhibition should come first.


In languages like Java, with checked exceptions, you are required by the compiler to have a try-catch block that handles the checked exception at some point, or you have to add "throws FooException" to your function, which passes over the error-handling responsibility to callers of that function.

Stack unwinding (with 'finally' clean-up blocks) results in higher brevity and relatively cleaner code than having a err != null check after every function call. Not to say, checking err != null is optional, could be accidentally forgotten, and cause unpredictable runtime behavior.


This is true, but I was referring to something like Scala's Try[T] (which wraps Java's exceptions and returns either the value T or the exception caught, and allows you to chain Try calls together through `map()`, `recover()` from them as appropriate, etc.) or Rust's Result<V, E>.


I agree with this except for two cases, which I would LOVE counter arguments for.

Case 1: checked exceptions. This is what Java used heavily in the early days, a bit less today, which basically requires you to deal with known exception scenarios. It was good because it wouldn't compile unless you explicitly said "I'm not dealing with this" or you dealt with it. But its bad because it requires your methods to throw some list of exceptions all the way up the call stack. I think there are some other problems that Experts(tm) around here can probably point out better than I. Still, checked exceptions seem to be a valid pattern in some contexts, and this feels like the same pattern Go follows. That said, I dislike Go's error handling for the same reason: my code is really cluttered and unclear this way.

Case 2: when you catch everything, you may not throw the right thing to your caller(s). Even worse, you may swallow an exception unintentionally and leave callers hanging in the balance. It's not that it's impossible or particularly difficult to avoid making these mistakes by wrapping everything in a Try[T], just that it's easy to make the mistake of catching more than you intended.


Like everything else in language design, it's a trade-off. The negative of the try/catch paradigm is that it's effectively a goto statement, and is often abused as such. Though I haven't worked professionally with a statically typed language with try/catch, I've spent many afternoons trying to find out exactly where an exception is caught (often six or seven frames down) in python frameworks like django, flask, and aiohttp. When exceptions are passed as values it's much more explicit.


There are only two err != nils in the article. What's your point?


Four, actually, but two have been removed for brevity.


Are you referring to the instances where error is a return value, but an error result under the known inputs would be impossible, so it would be pointless to check it? That's not exactly the same as removed for brevity.


Yes - just my quirky sense of humor! It isn't being documented as being safe (that I could find), so it should be checked IMO.


Does the Go standard library document that on any function like you are expecting?

The code for both functions make it pretty clear that an error will only be returned if the input is invalid, which is not something that will occur with the known constants being fed into them.


Yes, it does, where the guarantee exists: e.g. https://golang.org/pkg/bytes/#Buffer.Write

Looking at the implementation is no guarantee that the implementation won't be changed in future.


> Yes, it does, where the guarantee exists

That isn't quite the same thing. That says that an error is returned only because it is required to conform to the io.Writer interface. Without that requirement, it wouldn't return error in the first place. It tells the reader that they should not be confused as to why writing to a buffer might return an error, when such an operation should fundamentally not return an error.

These other functions in question have very good reasons to return an error: Invalid input.

> Looking at the implementation is no guarantee that the implementation won't be changed in future.

The Go compatibility promise says that the behaviour won't change, except under exceptional circumstances – like a security flaw that cannot be fixed using the original behaviour. It is highly unlikely that is an issue for those particular functions.

But, if you still think it is important, what do you plan to do with an error that might just randomly appear in the future anyway?


> But, if you still think it is important, what do you plan to do with an error that might just randomly appear in the future anyway?

Terminate processing and pass the error back up the call chain. This is basically what exceptions do, and basically what 'if err != nil { return err }' does.

One could also pause processing, interrogate the call chain for what to do about the error condition and the proceed as directed; this is what conditions & restarts do. IMHO it's a better solution, but it requires some syntax to be readable.


> Terminate processing and pass the error back up the call chain. This is basically what exceptions do, and basically what 'if err != nil { return err }' does.

The function in question that could return an error is being called in the main function. Who are you going to pass it to, exactly? error is a not a return value on main. If you knew what the error could be, you might find a suitable workaround under those conditions. But since the error does not even exist for you to handle, about all you can do is terminate the application. Which is exactly what will happen in this circumstance anyway, even if you don't check the error result.

I'm still not clear what you are really going to meaningfully do with the error information here even if you did magically start getting errors against the compatibility promise? If you are going to handle errors, you actually have to know what you want to do with them. The following adds nothing to your code.

    if err != nil {
        // I didn't expect that. Oh well.
    }
It's not like anyone is suggesting you should avoid checking errors in all circumstances. We're talking about very specific cases where all of the conditions are known. If GET stops being an HTTP request method, or http://example.com/ is no longer a valid URL, you've got way bigger problems than not gracefully handling the world flipping upside down.


> The function in question that could return an error is being called in the main function. Who are you going to pass it to, exactly?

The operating system: exit with a non-zero exit code, indicating the specific error.


Which is exactly what will happen as the code is written.


Two. The URL is a constant that is guaranteed not to return an error. It is not brevity.

Anyways, the point is I don't understand the point of err != nil bashing here, given there are two programs in the article that only contain one error check each.


As someone who writes a lot of Go I have mixed feelings about it. The problem is that there are implicit errors regardless and the explicitness in err != nil tricks beginners into thinking that they've covered all edge cases, they haven't. Recover() is probably the only way to handle internal panics and even then it only works if you know to that internal call might call panic().

That said I can't think of an alternative other then to reduce the number of panic()s hidden in various libraries.


Avoiding error checking against a properly formatted constant is not avoiding error checking. I cannot follow your argument.


Sorry, I guess I'm not really making one. If anything I'm commenting that (IMO) explicit error handling isn't as explicit as people thing it is. There are internal errors induced by panic() that are really inconvenient.


Panics are supposed to be launched on an "end all work on this; code path is FUBAR". On a web server that usually means dropping the connection, logging everything and firing an automated e-mail to the dev team to notify there's a bug in the code that needs to be fixed.

All other error conditions are expected and handled. That's what Go's error handling philosophy is all about.


panic should be a "the world is ending" sort of operation.


This is turning into a major tangent, but where is the guarantee documented?


People really hate GoLang's error checking because of the impression that there is a lot of repetition around err != nil statements.


People really hate handling errors


Well, the reason why I'm using a high-level programming language is there are a bunch of mechanical things that the computer will do better and more reliably than me. Otherwise I'd just write assembly.

What sorts of things fall into this category, and what tradeoffs you're willing to make to have the computer do things for you, is a matter of application (and taste), and so we reasonably have many different programming languages. But it's entirely defensible to want, in the abstract, your language to do repetitive and important work for you. That's what computers are good at.


There's not one reliable, or a set of reliable, way to handle errors. A high level language could handle it for you reliably in the sense of throwing exceptions and hoping you'll catch it. That'd work, you'd get Java software that throws exceptions all the time during normal operations.

Errors are valid values, handling them is as normal as handling other values you get from functions.


Rust's error handling:

    let foo = try!(something_that_might_error())
    // or soon...
    let foo = something_that_might_error()?
The try!() macro or the ? operator unwrap a Result<T, E> value, which is basically "either the return value or an error value". If it's a return value (T), the macro/operator just gets the value out of it - but if it's an error (E), it will convert the error into your own function's Result<T, E> return type using the From trait, and return that from your own function immediately.

The practical result? When I'm parsing input or doing other things that are heavy on errors, I can split it out into another function, wrap every call that might fail in try!(), and only have to actually handle the error once. This allows me to see my code clearer - I can see the success case very clearly, but I'm still forced to decide whether to handle or return errors by the compiler, because there's no other way to get the T out of a Result<T, E>.

There's a few other languages which implement this, though they lean towards the functional side a lot more than Rust does.

Experience: I'm writing a small internal certificate authority in Go because it has x.509 and signing support in the standard library. I'm no safer by having roughly 3/5 of my lines be if err != nil { return err; }.


That's actually a great counter example! Tho most day-to-day high level PL don't have a type system capable of offering this sort of checks!


Honestly... you could do it as a primitive type if you wanted. The only "special" bits needed are a piece of syntax which checks if it's an error, and if so returns it, else evaluates to the private value; and a way of unwrapping the actual error value when you want to handle it.

Go already has plenty of complicated language primitives with complex behaviour.


it's not that terrible in languages with abstraction


Like the ones where most people end up not actually handling errors where they matter.


Which languages specifically, and why do exceptions encourage you to drop errors on the floor?


I think people are misinterpreting support for try catch family in some high level languages as lazy approaches.

But truth is it is the programmers who are lazy, we should not blame language for providing a feature which some programmer abuse.

I have never seen a Java programmer profiling their code for Memory or CPU at work and I am here running my shitty C code under Valgrind and a profiler all the time.

I guess, it is programmers and not the language.

Go's error != nil approach though rudimentary actually forces lazy programmers to complain, since they cannot abuse it.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: