NGINX is not showing PHP 500 error log (nor does php-fpm)

Problem

I received a 500-error on my API request to a PHP backend. The result was returned as a 500 html status.

When searching the logs, there was no mention of this 500 error, other than in the access log of nginx.

Where does nginx or php-fpm put the backtrace of PHP errors?

Solution

Turns out, you need to enable catch_workers_output = yes in your www.conf file, typically located over at /etc/php/7.4/fpm/pool.d/

# file /etc/php/7.4/fpm/pool.d/www.conf
...
catch_workers_output = yes
...

The comments above that line explains it pretty well:

; Redirect worker stdout and stderr into main error log. If not set,
; stdout and stderr will be redirected to /dev/null according to
; FastCGI specs.
; Note: on highloaded environement, this can cause some delay in
; the page process time (several ms).
; Default Value: no

Add multiple languages to your React app with i18next

Install the needed packages

Install i18next

yarn add i18next \
  react-i18next \
  i18next-http-backend \
  i18next-browser-languagedetector

Configuration

Add Suspense

Wrap your code with <Suspense /> like the following:

// file: src/index.js
(...)
ReactDOM.render(
    <Suspense fallback="loading">
        <Router>
            <Switch>
                <Route 
                  exact 
                  path='/login' 
                  component={Login}
                />
                <Route component={App}/>
                <ToastContainer/>
            </Switch>
        </Router>
    </Suspense>,
    document.getElementById('root')
)

Initializing i18next in your app

Place the following initilisation code in a file i18n.js

// file: ./js/helpers/i18n.js
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from 'i18next-http-backend'
import LanguageDetector from 'i18next-browser-languagedetector'

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next) // passes i18n down to react-i18next
  .init({
    fallbackLng: 'en',
    debug: false,

    backend: {
      loadPath: '/locales/{{lng}}.json',
      // path to post missing resources
      addPath: '/locales/add/{{lng}}.json',
    },

    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default
    }
  });

export default i18n;

The parser

Add the config file in your root directory: ./i18next-parser.config.js

This will:

  • put the translatable strings inside ./public/locales/<language>.json
  • support en and nl countrycodes for language
  • use the key (the translatable string) as the translation when a new translatable string is found
// file: ./i18next-parser.config.js
module.exports = {
  
  // see below for more details
  lexers: {
    // hbs: ['HandlebarsLexer'],
    // handlebars: ['HandlebarsLexer'],

    htm: ['HTMLLexer'],
    html: ['HTMLLexer'],

    // mjs: ['JavascriptLexer'],
    js: ['JavascriptLexer'], // if you're writing jsx inside .js files, change this to JsxLexer
    ts: ['JavascriptLexer'],
    jsx: ['JsxLexer'],
    tsx: ['JsxLexer'],

    default: ['JavascriptLexer']
  },

  locales: ['en', 'nl'],
  // An array of the locales in your applications

  output: 'public/locales/$LOCALE.json',
  // output: 'locales/$LOCALE/$NAMESPACE.json',
  // Supports $LOCALE and $NAMESPACE injection
  // Supports JSON (.json) and YAML (.yml) file formats
  // Where to write the locale files relative to process.cwd()

  useKeysAsDefaultValue: true,
  // Whether to use the keys as the default value; ex. "Hello": "Hello", "World": "World"
  // This option takes precedence over the `defaultValue` and `skipDefaultValues` options

  verbose: true,
  // Display info about the parsing including some stats
}

Scan the code for translatable strings

Install the scanner application with npm.

npm install -g i18next-parser

Assuming that you placed the config file in the root of your project (i18next-parser.config.js), run the following command:

i18next --config ./i18next-parser.config.js \
    'src/**/*.js' \
    '!src/js/bootstrap.min.js' 

--config provide the path to your config. In this example it is not needed as the configuration has the default name and is in the default location
src/**/*.js provide the glob which finds all your *.js files. Add what you need (.ts, .jsx, etc.)
!src/js/bootstrap.min.js an example of how to exclude a certain file

Now you can find the translatable strings in the following files: ./public/locales/<language>.json

Change the language in the application

Change the language with i18next

// file: ./index.js
import './js/helpers/i18n'
import i18next from 'i18next'

...

// change the language
i18next.changeLanguage('nl')

Install oh-my-zsh on Mac with iterm2

When I followed the guides I found online on this topic, this was what I ended up with:

zsh with powerline showing squares with questionmarks

Clearly, something went wrong. The zsh shell was showing squares with questionmarks in it.

After fixing, this is what oh my zsh looks like in iterm on my Macbook Pro running macOS Catalina.

oh-my-zsh now showing icons correctly

Follow the instructions on this page to get the same result.

Install oh-my-zsh

Directions taken from: https://github.com/ohmyzsh/ohmyzsh

sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

Set the the theme

I used the powerlevel9k theme from here: https://github.com/Powerlevel9k/powerlevel9k/wiki/install-instructions

git clone https://github.com/bhilburn/powerlevel9k.git ~/.oh-my-zsh/custom/themes/powerlevel9k

Now select this theme in your ~/.zshrc file

ZSH_THEME="powerlevel9k/powerlevel9k"

Install the fonts

This is the solution to get rid of the squares with questionmarks in your oh-my-zsh installation.

It appears that the used font in the powerline uses characters which need a special / patched font. See: https://github.com/powerline/fonts

git clone https://github.com/powerline/fonts
cd fonts
./install.sh

Now configure your iTerm profile to use the Droid Sans Mono for Powerline font.

Go to Preferences > Profiles > Text > Font and select (or type) the Droid Sans Mono for Powerline font.

Sort an array in javascript by property of one of the object elements

Say you have an array consisting of objects. And you want to order that array by one of the properties of these objects.

Take this array:

[
    {
        id: 4,
        title: "Lorem ipsum",
        created: "2020-04-10T14:59:00+00:00"
    },
    {
        id: 2,
        title: "Iptum quanto",
        created: "2020-05-29T13:17:48+00:00"
    },
    {
        id: 1,
        title: "Dolor samet",
        created: "2020-05-29T13:16:20+00:00"
    },
    {
        id: 3,
        title: "Tenari fluptum",
        created: "2020-05-29T13:08:39+00:00"
    }
]

Let’s sort it by the string created property of each element

const orderedChapters = chapters.sort((a, b) => {
  return a.created.localeCompare(b.created))
}

OR sort it by the numerical id property of each element

const orderedChapters = chapters.sort((a, b) => {
  return (a.id > b.id) ? 1 : ((b.id > a.id) ? -1 : 0)
}

POST request turns into GET request

I was firing login request to my API but I could not get it to return the correct response.

Upon investigation, it turns out that the API received a GET request while I issued a POST request with Postman.

The issue? I was sending the POST request to http while the API was listening on https!

It turns out that the request was forwarded to the https address as a GET request…

Solution: send requests to the https address

Docker on Windows: /usr/bin/env: bash\r No such file or directory

Getting the following error when you want to start your Docker container on Windows: /usr/bin/env: bash\r : No such file or directory

[edit]

I discovered that another fix is to do dos2unix <file>

dos2unix <file with the issues>

Try that if you’re on Windows or working with others who work on Windows.

[/edit]

I found several topics with several fixes. But what fixed it for me was:

Solution: Setting the line-endings correctly

  • In my editor: \n
  • In git: git config --global core.autocrlf false

I chose the \n line ending as this is stated in PSR-12: 2.2 Files:
All PHP files MUST use the Unix LF (linefeed) line ending only.

I use PHPStorm and had to go to
Settings > Editor > Code Style > tab General > Unix and macOS (\n)

NOTE: you may have to do the following to fix files with the wrong line-endings:

  • remove the built Docker image on Windows: first list docker images and then delete: docker rmi <imagename>
  • fix the line-ending of the file. You might do that by removing the newline and adding a new one. Don’t forget to save, commit and push

Add mobx to your React project

Mobx helps you manage the state.

I set up mobx to be used as an import to your (functional) components. I do this, rather than using createContext() because the latter one only allows you to use it inside components, not functions.

To set up mobx you’ll need to:

  1. install mobx
  2. create store
  3. configure store and components

In this example, I make use of the function autoStore() in the constructor of the store to save the state to localStorage whenever there is a change to the state.
This makes that state is saved across page-refreshes.

install mobx

Install with:

yarn add mobx mobx-react

create store

Create your store by creating file src/stores/uiStore.tsx.

This will be one of the stores you can use. You can create as many stores as you want.

// file src/stores/uiStore.tsx
import {makeAutoObservable} from "mobx";

const uiStore = () => makeAutoObservable({
    showLocations: false as boolean,
    toggleShowLocations(){
        this.showLocations = ! this.showLocations
    },
})

export default uiStore

Next, we add this store to the context. This makes that we can access all stores via the useStore context.

// file ./src/stores/index.tsx
import {createContext, useContext} from "react";
import uiStore from "./uiStore";

const store = {
    ui: uiStore(),
}

export const StoreContext = createContext(store)

export const useStore = () => {
    return useContext<typeof store>(StoreContext)
}

export default store

In your App.tsx, add the StoreContext so every child component can make use of it.

// file ./src/App.tsx
...
import store, {StoreContext} from "../stores";
...

const MyApp = () => {
    ...

    return (
      <StoreContext.Provider value={store}>
            <Header />
            <Switch>
                  ...
            </Switch>
      </StoreContext.Provider>
    )
}

File ./src/libs/autoStore.js takes care of storing the state in localStorage.

// file ./src/libs/autoStore.tsx
import { toJS, autorun, set } from 'mobx'

export default function (_this: any, storeName='store') {
    let firstRun = true

    // will run on change
    autorun(() => {
        // on load check if there's an existing store on
        // localStorage and extend the store
        if (firstRun) {
            const existingStore = window.localStorage.getItem(storeName)

            if (existingStore) {
                set(_this, JSON.parse(existingStore))
            }
        }

        // from then on serialize and save to localStorage
        const serializedThis = toJS(_this)
        window.localStorage.setItem(
            storeName,
            JSON.stringify(serializedThis)
        )
    })

    firstRun = false
}

Use the store: configure store and components

Configure your components by wrapping them with the observer() function of mobx.

By doing this, mobx knows whenever something changes that concerns the state.

// file ./src/components/SayHello.js

import React, { Component } from 'react'
import { observer } from 'mobx-react-lite'
import {useStore} from "../../stores";

const SayHello = () => {
   const {ui} = useStore()

   return <div>{ui.showLocations && 'Hi locations!'}</div>
}

export default observer(SayHello)

Resources

Take a look at these pages. They provide useful information about working with mobx.

Caprover doesn’t set MySQL user or database

The problem: one-click app MySQL won’t create database or user

In caprover, formerly captainduckduck, I had the issue that the database and a user were not created by setting environment variables MYSQL_USER and MYSQL_DATABASE.

While building, this message was displayed by Docker:

[Warning] One or more build-args [MYSQL_USER, MYSQL_DATABASE] were not consumed

So appearently, Caprover does not support setting the environment variables for (only?) MySQL anymore.

Selecting MySQL from the oneclick-apps gives you the following options.
Note the absence of setting extra environment variables.

adding a MySQL one-click-app in Caprover

You can only set the root password.

When you start this new app, only the root password is set. If you now set the environment variables, MySQL will not create the database or user. It does this only on first startup, after creating the one-click-app for MySQL.

Solution: supply Dockerfile with the environment variables set

My workaround? Don’t use a one-click app and supply a Dockerfile directly inside Caprover instead.

This is what I used:

  1. Create a new app (and select ‘has persistent data‘)
  2. Open the newly created app by clicking on its name in the Apps overview
  3. Click tab ‘Deployment
  4. Scroll down to ‘Method 4: Deploy plain Dockerfile
  5. Insert and edit the following content and click on ‘Deploy Now
ENV MYSQL_ROOT_PASSWORD=S3cretR00t
ENV MYSQL_DATABASE=dbname_for_app
ENV MYSQL_USER=app_user
ENV MYSQL_PASSWORD=An0therS3cret
adding Dockerfile contents directly into your app

When you deploy your new database app now, the database and the user will be created.

Help your future self: set environment variables after the fact

TIP: after this, you won’t see the Dockerfile contents anymore. So in the future it might be unclear what values were set.
In order to overcome this, set the environment variables as well over at App Configs > Environmental variables:

MYSQL_ROOT_PASSWORD=S3cretR00t
MYSQL_DATABASE=dbname_for_app
MYSQL_USER=app_user
MYSQL_PASSWORD=An0therS3cret
add the environment variables in ‘bulk mode’

That’s it! Now let’s hope we’ll be able to set environment variables in the one-click setup some day soon again.

Install Gearman from source on Ubuntu 18.04

I tried to install Gearman on my Ubuntu 18.04 Docker container and encountered a lot of issues.

This write-up is to save you some valuable time of your life.

TL;DR

apt update
apt install -y g++ uuid-dev gperf libboost-all-dev libevent-dev curl 

cd /tmp
curl -LO https://github.com/gearman/gearmand/releases/download/1.1.18/gearmand-1.1.18.tar.gz
tar xvzf gearmand-1.1.18.tar.gz
cd gearmand-1.1.18
./configure --with-boost-libdir=/usr/lib/x86_64-linux-gnu
make
make test
make install

# Start gearman in the background
gearmand --log-file /var/log/gearmand.log --daemon

To get there, I encountered the following issues:

configure: error: Unable to find libevent

./configure --with-boost-libdir=/usr/lib/x86_64-linux-gnu
...
checking test for a working libevent... no
configure: error: Unable to find libevent

Fix: apt install -y libevent-dev

configure: error: Could not find a version of the library!

./configure --with-boost-libdir=/usr/lib/x86_64-linux-gnu
...
checking whether the Boost::Program_Options library is available... yes
configure: error: Could not find a version of the library!

Fix: apt install -y libboost-all-dev

Installing the PHP client libraries for Gearman

If you intend to use PHP to talk to your Gearman service, use the following to get the libraries installed.

Note: you might not need the last line. This is to enable the Gearman PHP extension in Docker.

cd /tmp \
  && wget https://github.com/wcgallego/pecl-gearman/archive/gearman-2.0.6.tar.gz\
  && tar xvzf gearman-2.0.6.tar.gz \
  && mv pecl-gearman-gearman-2.0.6 pecl-gearman \
  && cd pecl-gearman \
  && phpize \
  && ./configure \
  && make -j$(nproc) \
  && make install \
  && cd / \
  && rm /tmp/gearman-2.0.6.tar.gz \
  && rm -r /tmp/pecl-gearman \
  && docker-php-ext-enable gearman