5 Reasons to Quit and 5 Reasons to Stay

In 1977 the Apple II, TRS-80 and the Commodore PET were released. In the same year David Allen Coe wrote a song for Johnny Paycheck called Take This Job and Shove It. (listen on YouTube) The hit single sold over two million copies and inspired a movie by the same title. I have no doubt that it inspired a lot of people to quit their job and find something better.

image
(from the album image on Amazon)

I was entering junior high school then but having grown up on a farm with the radio tuned to country music, the tune and the words stuck. In my own career I have made the choice to quit or to stay a number of times. Early in my career I burned a bridge that I later regretted. Since then I have not left a job in a way that I have later come to regret, although I have sometimes come to regret leaving the job itself.

What then should you use as criteria for quitting a job or staying in a job where you are thinking about quitting? Obviously the answer to this question will be deeply subjective, so I want you to consider that you ought to make such a choice based on much more than my opinion or an old song. With that caveat in place, let's dive into my personal opinions.

Why Quit

  1. Unhappy -- This seems obvious. If you're unhappy, you might be in the wrong job. But before you hand in your resignation, ask yourself why you're unhappy. Maybe even consult with a professional. Is it really the job that is making you unhappy? Or is it something about yourself that you could change? If you find yourself in a toxic environment and you cannot alter it, save your sanity and get out.
  2. Bored -- The job is unchallenging. You find yourself more interested in checking Facebook than you do your work. You complete your tasks without struggle and without learning anything new. You spend more time wondering what you'll have for lunch than you do about how to complete your work.
  3. Starving -- If you are not getting paid what you need to pay the bills or achieve your financial goals, ask yourself if you could do better. The most likely answer is that you can if you are willing to take some risk. Those may be big IFs for you, so proceed with care. And if you find yourself stuck, perhaps you should think of getting more education or starting a business on the side.
  4. Unsatisfied -- While it may seem to be the same, this is not the same as being unhappy. You may enjoy your job and the people you work with immensely but you may find that you are still unsatisfied. This may be because you find your work lacking significance or purpose. You may feel as if you're not contributing value to the community you value. A friend told me recently that we ought to, "Do those things that last, not those things that should be last."
  5. More -- Finally one good reason, in my opinion, to quit your job is that you want more from your career. It may be that you want to work in a different place or industry. You may want to work with a company led by people of a different caliber or with different goals and principles that you more readily identify with. Perhaps you want to explore an entirely different line of work. My father used to tell me, when assigning me a new task on the farm and in response to my complaints of being too tired, "A change is as good as a rest." You may be perfectly satisfied in your job but yearn for a new adventure. Perhaps you are a content hobbit and a band of dwarves come along and entice you to join a new startup far away and over the mountains.

Why Stay

  1. Finishing -- You may have good reasons to leave but a compelling reason to stay is to finish what you've started. If you hate to leave things incomplete or you want to simply see your current project succeed, you can stuff all the reasons you want to or ought to quit into a box and get them out after you're finished. They'll likely still be there when you're done.
  2. Stability -- This is where you should do what I say and not what I do. Sometimes I have left a stable job for one of the above reasons and later have thought that I ought to have stayed and made my life less chaotic for myself and my family. Too many job changes in my career has at times been a barrier for potential hiring managers. Take care when you choose to quit. Sometimes staying for another year will open up greater opportunities down the road.
  3. Learning -- Often the reason we want to quit is exactly the reason we ought to stay. The challenges we face in a job provide an ongoing opportunity to learn and grow. Running away from that because it's hard is often, if not always, a mistake. If you believe that you have more to learn and contribute, it may be to your advantage to stick it out.
  4. Opportunity -- Many employers promote from within. Some do not. If you have future opportunities coming your way, or even a likelihood of such, it may be worth putting up with whatever is bothering you and kicking things up a notch to get noticed and get promoted. Patience and hard work nearly always pay off. On the other hand, I have watched a few of my peers stay in a job far too long and pass on opportunities for growth in a lateral move, falling behind in growth and financial rewards.
  5. Anchor -- Your interests in other areas of your life may require you to have an anchor, a job you can count on even if you are not entirely satisfied. You may be serving your community or writing a book or caring for children or another family member. If keeping a job that otherwise you might leave in order to fulfill this other part of your life is necessary, then find a way to be at peace with that decision. It's not wrong to need the anchor or to choose another priority over your career.

Making Up Your Mind

Sometimes it's an easy choice. Sometimes it's much harder. Keep in mind that it's easier to find a job when you have a job. Reach out to those in your life that you care about and that depend on you. Get their support for your decision if you can. Get their input. They may be seeing the next move on the chessboard from the sideline when you cannot.

Weigh the pros and cons. Don't let a recruiter pressure you into making a decision. Don't make a decision because you're angry or otherwise dealing with powerful emotions that could lead you into making a bad decision. Take your time. Make a decision and then move forward. Don't consider an offer from an employer to keep you unless the only reason you are leaving is financial, and even then, take care. Once you take another offer, be sure to stick with it.

Good luck!

HttpContext and Logging to Elasticsearch on a Background Thread

“HttpContext is not thread-safe. Reading or writing properties of the HttpContext outside of processing a request can result in a NullReferenceException.” (from docs.microsoft.com)

image

I am a big fan of Elasticsearch (ELK) logging and have built this into the Ioka.Services.Foundation using the Serilog libary, my current favorite .NET Core logging library. There are many ways to approach this and my intent is not to explore those but to show one way of taking on the task of logging in a background thread while preserving some request context detail in your log.

Following the recommendation on the above docs page, we copy the elements of the HttpContext that we need for our background thread logging using the LogContextFactory. Here’s a snippet of that code. For your scenario, you’ll want to modify what values you wish to preserve and you may wish to remove the fairly heavy duty user agent parser if you don’t care about seeing user agent data broken down in the log message.

public static LogContext Create(HttpContext context)
{
   return CreateFactory(() => context)();
}

public static Func CreateFactory(Func httpContextFactory)
{
   if (null == httpContextFactory) throw new ArgumentNullException(nameof(httpContextFactory));
   return new Func(() =>
   {
      try
      {
         var httpCtx = httpContextFactory();
         var httpRequestFeature = httpCtx.Request.HttpContext.Features.Get();
         var context = new LogContext();
         context["_ThreadId"] = Environment.CurrentManagedThreadId.ToString(); 
         context["_Source"] = Assembly.GetEntryAssembly().GetName().Name;
         context["_IpAddress"] = httpCtx.Connection.RemoteIpAddress.ToString();
         context["_UserId"] = httpCtx.Request.Headers["APPUID"].Count > 0 
            ? httpCtx.Request.Headers["APPUID"][0] 
            : context["_UserId"];
         context["_HttpMethod"] = httpCtx.Request.Method;

In the controller we call the Create method to get a copy of what we need to pass into the background thread async method called DoMathWithLogging (cheesy name for demo purposes only) like this:

public async Task<ActionResult<IEnumerable<string>>> Get()
{
    var msg1 = "Another message";
    var msg3 = new CustomError { Name = "Second", Message = "Second other message" };
    _logger.Debug("This is a debug message. {msg1}, {@msg3}", msg1, msg3);

    var logContext = LogContextFactory.Create(this.HttpContext);
    var result = await _mathDemoProvider.DoMathWithLogging(logContext, _logger);

    return new string[] 
    {
        logContext["_UserId"],
        logContext["_RequestId"],
        result.ToString()
    };
}

Now in the DoMathWithLogging method, we use the ILog interface With method to pass the LogContext object into the logger to preserve what we have copied from HttpContext to the LogContext object.

public async Task<long> DoMathWithLogging(LogContext logContext, ILog logger)
{
    long x = 0;
    try
    {
        var rand = new Random();
        for (int i = 0; i < 10; i++)
        {
            x = 1000 * (long)rand.NextDouble();
            Thread.Sleep(10);
        }
        Thread.Sleep(100);
        var c = 0;
        x = 77 / c;
    }
    catch (Exception e)
    {
        //uses new logger with saved context as this 
        //is not on the request background thread
        logger.With(logContext).Error(e, "Error: value of {LargeValue}", x);
    }
    return x;
}

Note that in our demo code, we deliberately throw a divide by zero error and log it. And now in the implementation of the With method looks like this, capturing the current thread in the “_ThreadId-With” property on the context.

public ILog With(LogContext context)
{
    context["_ThreadId-With"] = Environment.CurrentManagedThreadId.ToString();
    var list = _enrichers.Where(x => x.GetType() != typeof(LogEnricher)).ToList();
    list.Insert(0, new LogEnricher(context, null));
    return new Log(_config, _level, () => _index, _failureSink, _failureCallback, list.ToArray());
}

In the With method, we insert a new log enricher for the Serilog logger. This allows us to capture the copied context values in the log messages, such as the Error logged like this:

{
  "_index": "test",
  "_type": "logevent",
  "_id": "JAdXF2oB9fWPG6gy8H_9",
  "_version": 1,
  "_score": null,
  "_source": {
    "@timestamp": "2019-04-13T15:36:40.2296224+00:00",
    "level": "Error",
    "messageTemplate": "Error: value of {LargeValue}",
    "message": "Error: value of 0",
    "exception": {
      "Depth": 0,
      "ClassName": "",
      "Message": "Attempted to divide by zero.",
      "Source": "Ioka.Services.Demo",
      "StackTraceString": "   at Ioka.Services.Demo.Providers.MathLoggingDemoProvider.DoMathWithLogging(LogContext logContext, ILog logger) in D:\\Code\\Github\\Ioka.Services.Foundation\\src\\Ioka.Services.Demo\\Providers\\MathLoggingDemoProvider.cs:line 30",
      "RemoteStackTraceString": "",
      "RemoteStackIndex": -1,
      "HResult": -2147352558,
      "HelpURL": null
    },
    "fields": {
      "LargeValue": 0,
      "_UserId": "root",
      "_IpAddress": "::ffff:172.18.0.1",
      "_Source": "Ioka.Services.Demo",
      "_MachineName": "6cf7fdb5f3cf",
      "_ThreadId": "14",
      "_HttpMethod": "GET",
      "_RequestId": "50d32de9-df69-4aee-ae48-075f22b8ac2d",
      "_Url": "https://localhost:44370/api/Values",
      "_Query": "Microsoft.AspNetCore.Http.Internal.QueryCollection",
      "_WebUser": null,
      "_Browser": "Chrome 73.0.3683 Windows 10",
      "_Header_Connection": "keep-alive",
      "_Header_Accept": "text/plain",
      "_Header_Accept-Encoding": "gzip, deflate, br",
      "_Header_Accept-Language": "en-US,en;q=0.9",
      "_Header_Host": "localhost:44370",
      "_Header_Referer": "https://localhost:44370/api-docs/index.html",
      "_Header_User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36",
      "_ThreadId-With": "14"
    }
  },
  "fields": {
    "@timestamp": [
      "2019-04-13T15:36:40.229Z"
    ]
  },
  "sort": [
    1555169800229
  ]
}

Note the "_ThreadId-With" property above has the same vs!he as the request thread is. This is because not all async methods will run on a background thread. In previous versions of this code, I forced a long running task to spin up on another thread to verify this. Generally I would not recommend that in practice.

Also note the "_RequestId" property which would allow you to filter in Kibana for all entries with that value in order to trace all log entries for a given request. This can be a very useful tool when you're trying to figure out what happened.

This is the first of many upcoming posts on the code and use of the Ioka.Services.Foundation. The code base is not intended for production use but to provide a guide when you create your own internal libraries to spin up a set of .NET Core microservices that have a common set of fundamental elements that will make working on them, supporting them and using them easier.